Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/thermal/pmic8901-tm.c b/drivers/thermal/pmic8901-tm.c
new file mode 100644
index 0000000..0ff5788
--- /dev/null
+++ b/drivers/thermal/pmic8901-tm.c
@@ -0,0 +1,594 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+/*
+ * Qualcomm PMIC8901 Thermal Manager driver
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/thermal.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mfd/pmic8901.h>
+
+/* PMIC8901 TEMP_ALRM registers */
+#define SSBI_REG_TEMP_ALRM_CTRL 0x23
+#define SSBI_REG_TEMP_ALRM_PWM 0x24
+
+/* TEMP_ALRM_CTRL */
+#define PM8901_TEMP_ST3_SD 0x80
+#define PM8901_TEMP_ST2_SD 0x40
+#define PM8901_TEMP_STATUS_MASK 0x30
+#define PM8901_TEMP_STATUS_SHIFT 4
+#define PM8901_TEMP_THRESH_MASK 0x0C
+#define PM8901_TEMP_THRESH_SHIFT 2
+#define PM8901_TEMP_OVRD_ST3 0x02
+#define PM8901_TEMP_OVRD_ST2 0x01
+#define PM8901_TEMP_OVRD_MASK 0x03
+
+#define PM8901_TEMP_STAGE_STEP 20000 /* Stage step: 20 C */
+#define PM8901_TEMP_STAGE_HYSTERESIS 2000
+
+#define PM8901_TEMP_THRESH_MIN 105000 /* Threshold Min: 105 C */
+#define PM8901_TEMP_THRESH_STEP 5000 /* Threshold step: 5 C */
+
+/* TEMP_ALRM_PWM */
+#define PM8901_TEMP_PWM_EN_MASK 0xC0
+#define PM8901_TEMP_PWM_EN_SHIFT 6
+#define PM8901_TEMP_PWM_PER_PRE_MASK 0x38
+#define PM8901_TEMP_PWM_PER_PRE_SHIFT 3
+#define PM8901_TEMP_PWM_PER_DIV_MASK 0x07
+#define PM8901_TEMP_PWM_PER_DIV_SHIFT 0
+
+/* Trips: from critical to less critical */
+#define PM8901_TRIP_STAGE3 0
+#define PM8901_TRIP_STAGE2 1
+#define PM8901_TRIP_STAGE1 2
+#define PM8901_TRIP_NUM 3
+
+/* Used because there is no means to read the die temperature */
+#define DEFAULT_NO_ADC_TEMP 37000
+
+struct pm8901_tm_device {
+ struct pm8901_chip *pm_chip;
+ struct thermal_zone_device *tz_dev;
+ unsigned long temp;
+ enum thermal_device_mode mode;
+ unsigned int thresh;
+ unsigned int stage;
+ unsigned int irq;
+ unsigned int hi_irq;
+};
+
+enum pmic_thermal_override_mode {
+ SOFTWARE_OVERRIDE_DISABLED = 0,
+ SOFTWARE_OVERRIDE_ENABLED,
+};
+
+static inline int pm8901_tm_read_ctrl(struct pm8901_chip *chip, u8 *reg)
+{
+ int rc;
+
+ rc = pm8901_read(chip, SSBI_REG_TEMP_ALRM_CTRL, reg, 1);
+ if (rc)
+ pr_err("%s: pm8901_read FAIL: rc=%d\n", __func__, rc);
+
+ return rc;
+}
+
+static inline int pm8901_tm_write_ctrl(struct pm8901_chip *chip, u8 reg)
+{
+ int rc;
+
+ rc = pm8901_write(chip, SSBI_REG_TEMP_ALRM_CTRL, ®, 1);
+ if (rc)
+ pr_err("%s: pm8901_write FAIL: rc=%d\n", __func__, rc);
+
+ return rc;
+}
+
+static inline int pm8901_tm_read_pwm(struct pm8901_chip *chip, u8 *reg)
+{
+ int rc;
+
+ rc = pm8901_read(chip, SSBI_REG_TEMP_ALRM_PWM, reg, 1);
+ if (rc)
+ pr_err("%s: pm8901_read FAIL: rc=%d\n", __func__, rc);
+
+ return rc;
+}
+
+static inline int pm8901_tm_write_pwm(struct pm8901_chip *chip, u8 reg)
+{
+ int rc;
+
+ rc = pm8901_write(chip, SSBI_REG_TEMP_ALRM_PWM, ®, 1);
+ if (rc)
+ pr_err("%s: pm8901_write FAIL: rc=%d\n", __func__, rc);
+
+ return rc;
+}
+
+static inline int
+pm8901_tm_shutdown_override(struct pm8901_chip *chip,
+ enum pmic_thermal_override_mode mode)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8901_tm_read_ctrl(chip, ®);
+ if (rc < 0)
+ return rc;
+
+ reg &= ~(PM8901_TEMP_OVRD_MASK | PM8901_TEMP_STATUS_MASK);
+ if (mode == SOFTWARE_OVERRIDE_ENABLED)
+ reg |= (PM8901_TEMP_OVRD_ST3 | PM8901_TEMP_OVRD_ST2) &
+ PM8901_TEMP_OVRD_MASK;
+
+ rc = pm8901_tm_write_ctrl(chip, reg);
+
+ return rc;
+}
+
+/*
+ * This function initializes the internal temperature value based on only the
+ * current thermal stage and threshold.
+ */
+static int pm8901_tm_init_temp(struct pm8901_tm_device *tm)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8901_tm_read_ctrl(tm->pm_chip, ®);
+ if (rc < 0)
+ return rc;
+
+ tm->stage = (reg & PM8901_TEMP_STATUS_MASK) >> PM8901_TEMP_STATUS_SHIFT;
+ tm->thresh = (reg & PM8901_TEMP_THRESH_MASK) >>
+ PM8901_TEMP_THRESH_SHIFT;
+
+ if (tm->stage) {
+ tm->temp = tm->thresh * PM8901_TEMP_THRESH_STEP +
+ (tm->stage - 1) * PM8901_TEMP_STAGE_STEP +
+ PM8901_TEMP_THRESH_MIN;
+ } else
+ tm->temp = DEFAULT_NO_ADC_TEMP;
+
+ return 0;
+}
+
+/*
+ * This function updates the internal temperature value based on the
+ * current thermal stage and threshold as well as the previous stage
+ */
+static int pm8901_tm_update_temp(struct pm8901_tm_device *tm)
+{
+ unsigned int stage;
+ int rc;
+ u8 reg;
+
+ rc = pm8901_tm_read_ctrl(tm->pm_chip, ®);
+ if (rc < 0)
+ return rc;
+
+ stage = (reg & PM8901_TEMP_STATUS_MASK) >> PM8901_TEMP_STATUS_SHIFT;
+ tm->thresh = (reg & PM8901_TEMP_THRESH_MASK) >>
+ PM8901_TEMP_THRESH_SHIFT;
+
+ if (stage > tm->stage) {
+ /* increasing stage, use lower bound */
+ tm->temp = (stage-1) * PM8901_TEMP_STAGE_STEP +
+ tm->thresh * PM8901_TEMP_THRESH_STEP +
+ PM8901_TEMP_STAGE_HYSTERESIS +
+ PM8901_TEMP_THRESH_MIN;
+ } else if (stage < tm->stage) {
+ /* decreasing stage, use upper bound */
+ tm->temp = stage * PM8901_TEMP_STAGE_STEP +
+ tm->thresh * PM8901_TEMP_THRESH_STEP -
+ PM8901_TEMP_STAGE_HYSTERESIS +
+ PM8901_TEMP_THRESH_MIN;
+ }
+
+ tm->stage = stage;
+
+ return 0;
+}
+
+static int pm8901_tz_get_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct pm8901_tm_device *tm = thermal->devdata;
+ int rc;
+
+ if (!tm || !temp)
+ return -EINVAL;
+
+ rc = pm8901_tm_update_temp(tm);
+ if (rc < 0)
+ return rc;
+
+ *temp = tm->temp;
+
+ return 0;
+}
+
+static int pm8901_tz_get_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode *mode)
+{
+ struct pm8901_tm_device *tm = thermal->devdata;
+
+ if (!tm || !mode)
+ return -EINVAL;
+
+ *mode = tm->mode;
+
+ return 0;
+}
+
+static int pm8901_tz_set_mode(struct thermal_zone_device *thermal,
+ enum thermal_device_mode mode)
+{
+ struct pm8901_tm_device *tm = thermal->devdata;
+
+ if (!tm)
+ return -EINVAL;
+
+ if (mode != tm->mode) {
+ pr_info("%s: mode: %d --> %d\n", __func__, tm->mode, mode);
+
+ if (mode == THERMAL_DEVICE_ENABLED)
+ pm8901_tm_shutdown_override(tm->pm_chip,
+ SOFTWARE_OVERRIDE_ENABLED);
+ else
+ pm8901_tm_shutdown_override(tm->pm_chip,
+ SOFTWARE_OVERRIDE_DISABLED);
+ }
+ tm->mode = mode;
+
+ return 0;
+}
+
+static int pm8901_tz_get_trip_type(struct thermal_zone_device *thermal,
+ int trip, enum thermal_trip_type *type)
+{
+ struct pm8901_tm_device *tm = thermal->devdata;
+
+ if (!tm || trip < 0 || !type)
+ return -EINVAL;
+
+ switch (trip) {
+ case PM8901_TRIP_STAGE3:
+ *type = THERMAL_TRIP_CRITICAL;
+ break;
+ case PM8901_TRIP_STAGE2:
+ *type = THERMAL_TRIP_HOT;
+ break;
+ case PM8901_TRIP_STAGE1:
+ *type = THERMAL_TRIP_HOT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int pm8901_tz_get_trip_temp(struct thermal_zone_device *thermal,
+ int trip, unsigned long *temp)
+{
+ struct pm8901_tm_device *tm = thermal->devdata;
+ int thresh_temp;
+
+ if (!tm || trip < 0 || !temp)
+ return -EINVAL;
+
+ thresh_temp = tm->thresh * PM8901_TEMP_THRESH_STEP +
+ PM8901_TEMP_THRESH_MIN;
+
+ switch (trip) {
+ case PM8901_TRIP_STAGE3:
+ thresh_temp += 2 * PM8901_TEMP_STAGE_STEP;
+ break;
+ case PM8901_TRIP_STAGE2:
+ thresh_temp += PM8901_TEMP_STAGE_STEP;
+ break;
+ case PM8901_TRIP_STAGE1:
+ break;
+ default:
+ return -EINVAL;
+ }
+ *temp = thresh_temp;
+
+ return 0;
+}
+
+static int pm8901_tz_get_crit_temp(struct thermal_zone_device *thermal,
+ unsigned long *temp)
+{
+ struct pm8901_tm_device *tm = thermal->devdata;
+
+ if (!tm || !temp)
+ return -EINVAL;
+
+ *temp = tm->thresh * PM8901_TEMP_THRESH_STEP +
+ PM8901_TEMP_THRESH_MIN + 2 * PM8901_TEMP_STAGE_STEP;
+
+ return 0;
+}
+
+static struct thermal_zone_device_ops pm8901_thermal_zone_ops = {
+ .get_temp = pm8901_tz_get_temp,
+ .get_mode = pm8901_tz_get_mode,
+ .set_mode = pm8901_tz_set_mode,
+ .get_trip_type = pm8901_tz_get_trip_type,
+ .get_trip_temp = pm8901_tz_get_trip_temp,
+ .get_crit_temp = pm8901_tz_get_crit_temp,
+};
+
+static irqreturn_t pm8901_tm_isr(int irq, void *data)
+{
+ struct pm8901_tm_device *tm = data;
+ int rc;
+ u8 reg;
+
+ rc = pm8901_tm_update_temp(tm);
+ if (rc < 0)
+ goto isr_handled;
+
+ rc = pm8901_tm_read_ctrl(tm->pm_chip, ®);
+ if (rc < 0)
+ goto isr_handled;
+
+ pr_info("%s: Temp Alarm - stage=%u, threshold=%u, temp=%lu\n",
+ __func__, tm->stage, tm->thresh, tm->temp);
+
+ if (reg & (PM8901_TEMP_ST2_SD | PM8901_TEMP_ST3_SD)) {
+ reg &= ~(PM8901_TEMP_ST2_SD | PM8901_TEMP_ST3_SD |
+ PM8901_TEMP_STATUS_MASK);
+
+ pm8901_tm_write_ctrl(tm->pm_chip, reg);
+ }
+
+ thermal_zone_device_update(tm->tz_dev);
+
+ /* Notify user space */
+ if (tm->mode == THERMAL_DEVICE_ENABLED)
+ kobject_uevent(&tm->tz_dev->device.kobj, KOBJ_CHANGE);
+
+isr_handled:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8901_tm_isr1(int irq, void *data)
+{
+ struct pm8901_tm_device *tm = data;
+ irqreturn_t rc;
+
+ disable_irq(tm->hi_irq);
+ rc = pm8901_tm_isr(irq, data);
+ enable_irq(tm->hi_irq);
+
+ return rc;
+}
+
+static irqreturn_t pm8901_tm_isr2(int irq, void *data)
+{
+ struct pm8901_tm_device *tm = data;
+ irqreturn_t rc;
+
+ disable_irq(tm->irq);
+ rc = pm8901_tm_isr(irq, data);
+ enable_irq(tm->irq);
+
+ return rc;
+}
+
+static int pm8901_tm_init_reg(struct pm8901_tm_device *tm)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8901_tm_init_temp(tm);
+ if (rc < 0)
+ return rc;
+
+ /* Use temperature threshold set 0: (105, 125, 145) */
+ tm->thresh = 0;
+ reg = (tm->thresh << PM8901_TEMP_THRESH_SHIFT) &
+ PM8901_TEMP_THRESH_MASK;
+ rc = pm8901_tm_write_ctrl(tm->pm_chip, reg);
+ if (rc < 0)
+ return rc;
+
+ /*
+ * Set the PMIC alarm module PWM to have a frequency of 8 Hz. This
+ * helps cut down on the number of unnecessary interrupts fired when
+ * changing between thermal stages. Also, Enable the over temperature
+ * PWM whenever the PMIC is enabled.
+ */
+ reg = 1 << PM8901_TEMP_PWM_EN_SHIFT |
+ 3 << PM8901_TEMP_PWM_PER_PRE_SHIFT |
+ 3 << PM8901_TEMP_PWM_PER_DIV_SHIFT;
+
+ rc = pm8901_tm_write_pwm(tm->pm_chip, reg);
+
+ return rc;
+}
+
+static int __devinit pmic8901_tm_probe(struct platform_device *pdev)
+{
+ struct pm8901_tm_device *tmdev;
+ struct pm8901_chip *pm_chip;
+ unsigned int irq, hi_irq;
+ int rc;
+
+ pm_chip = dev_get_drvdata(pdev->dev.parent);
+ if (pm_chip == NULL) {
+ pr_err("%s: no driver data passed in.\n", __func__);
+ return -EFAULT;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (!irq) {
+ pr_err("%s: no IRQ passed in.\n", __func__);
+ return -EFAULT;
+ }
+ hi_irq = platform_get_irq(pdev, 1);
+ if (!hi_irq) {
+ pr_err("%s: no HI IRQ passed in.\n", __func__);
+ return -EFAULT;
+ }
+
+ tmdev = kzalloc(sizeof *tmdev, GFP_KERNEL);
+ if (tmdev == NULL) {
+ pr_err("%s: kzalloc() failed.\n", __func__);
+ return -ENOMEM;
+ }
+
+ tmdev->pm_chip = pm_chip;
+ tmdev->tz_dev = thermal_zone_device_register("pm8901_tz",
+ PM8901_TRIP_NUM, tmdev,
+ &pm8901_thermal_zone_ops,
+ 0, 0, 0, 0);
+ if (tmdev->tz_dev == NULL) {
+ pr_err("%s: thermal_zone_device_register() failed.\n",
+ __func__);
+ kfree(tmdev);
+ return -ENODEV;
+ }
+
+ rc = pm8901_tm_init_reg(tmdev);
+ pm8901_tm_shutdown_override(tmdev->pm_chip, SOFTWARE_OVERRIDE_DISABLED);
+ if (rc < 0) {
+ thermal_zone_device_unregister(tmdev->tz_dev);
+ kfree(tmdev);
+ return rc;
+ }
+
+ /* start in HW control, switch to SW control when user changes mode */
+ tmdev->mode = THERMAL_DEVICE_DISABLED;
+ thermal_zone_device_update(tmdev->tz_dev);
+
+ platform_set_drvdata(pdev, tmdev);
+
+ rc = request_threaded_irq(irq, pm8901_tm_isr1, NULL,
+ IRQF_TRIGGER_RISING | IRQF_DISABLED,
+ "pm8901-tm-irq", tmdev);
+ if (rc < 0) {
+ pr_err("%s: request_threaded_irq(%d) FAIL: %d\n",
+ __func__, irq, rc);
+
+ thermal_zone_device_unregister(tmdev->tz_dev);
+ platform_set_drvdata(pdev, tmdev->pm_chip);
+ kfree(tmdev);
+ return -ENODEV;
+ }
+ tmdev->irq = irq;
+
+ rc = request_threaded_irq(hi_irq, pm8901_tm_isr2, NULL,
+ IRQF_TRIGGER_RISING | IRQF_DISABLED,
+ "pm8901-tm-irq2", tmdev);
+ if (rc < 0) {
+ pr_err("%s: request_threaded_irq(%d) FAIL: %d\n",
+ __func__, hi_irq, rc);
+
+ free_irq(irq, tmdev);
+ thermal_zone_device_unregister(tmdev->tz_dev);
+ platform_set_drvdata(pdev, tmdev->pm_chip);
+ kfree(tmdev);
+ return -ENODEV;
+ }
+ tmdev->hi_irq = hi_irq;
+
+ pr_notice("%s: OK\n", __func__);
+ return 0;
+}
+
+static int __devexit pmic8901_tm_remove(struct platform_device *pdev)
+{
+ struct pm8901_tm_device *tmdev = platform_get_drvdata(pdev);
+
+ free_irq(tmdev->hi_irq, tmdev);
+ free_irq(tmdev->irq, tmdev);
+ thermal_zone_device_unregister(tmdev->tz_dev);
+ platform_set_drvdata(pdev, tmdev->pm_chip);
+ pm8901_tm_shutdown_override(tmdev->pm_chip, SOFTWARE_OVERRIDE_DISABLED);
+ kfree(tmdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pmic8901_tm_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pm8901_tm_device *tm = platform_get_drvdata(pdev);
+
+ pm8901_tm_shutdown_override(tm->pm_chip, SOFTWARE_OVERRIDE_DISABLED);
+
+ return 0;
+}
+
+static int pmic8901_tm_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pm8901_tm_device *tm = platform_get_drvdata(pdev);
+
+ pm8901_tm_init_temp(tm);
+
+ if (tm->mode == THERMAL_DEVICE_ENABLED)
+ pm8901_tm_shutdown_override(tm->pm_chip,
+ SOFTWARE_OVERRIDE_ENABLED);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pmic8901_tm_pm_ops = {
+ .suspend = pmic8901_tm_suspend,
+ .resume = pmic8901_tm_resume,
+};
+
+#define PM8901_TM_PM_OPS (&pmic8901_tm_pm_ops)
+#else
+#define PM8901_TM_PM_OPS NULL
+#endif
+
+static struct platform_driver pmic8901_tm_driver = {
+ .probe = pmic8901_tm_probe,
+ .remove = __devexit_p(pmic8901_tm_remove),
+ .driver = {
+ .name = "pm8901-tm",
+ .owner = THIS_MODULE,
+ .pm = PM8901_TM_PM_OPS,
+ },
+};
+
+static int __init pm8901_tm_init(void)
+{
+ return platform_driver_register(&pmic8901_tm_driver);
+}
+
+static void __exit pm8901_tm_exit(void)
+{
+ platform_driver_unregister(&pmic8901_tm_driver);
+}
+
+module_init(pm8901_tm_init);
+module_exit(pm8901_tm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8901 Thermal Manager driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:pmic8901-tm");