Merge branch 'release' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
[~shefty/rdma-dev.git] / drivers / thermal / exynos_thermal.c
index bada130..e04ebd8 100644 (file)
@@ -82,7 +82,7 @@
 
 #define EXYNOS_TRIMINFO_RELOAD         0x1
 #define EXYNOS_TMU_CLEAR_RISE_INT      0x111
-#define EXYNOS_TMU_CLEAR_FALL_INT      (0x111 << 16)
+#define EXYNOS_TMU_CLEAR_FALL_INT      (0x111 << 12)
 #define EXYNOS_MUX_ADDR_VALUE          6
 #define EXYNOS_MUX_ADDR_SHIFT          20
 #define EXYNOS_TMU_TRIP_MODE_SHIFT     13
 #define SENSOR_NAME_LEN        16
 #define MAX_TRIP_COUNT 8
 #define MAX_COOLING_DEVICE 4
+#define MAX_THRESHOLD_LEVS 4
 
 #define ACTIVE_INTERVAL 500
 #define IDLE_INTERVAL 10000
 #define MCELSIUS       1000
 
+#ifdef CONFIG_EXYNOS_THERMAL_EMUL
+#define EXYNOS_EMUL_TIME       0x57F0
+#define EXYNOS_EMUL_TIME_SHIFT 16
+#define EXYNOS_EMUL_DATA_SHIFT 8
+#define EXYNOS_EMUL_DATA_MASK  0xFF
+#define EXYNOS_EMUL_ENABLE     0x1
+#endif /* CONFIG_EXYNOS_THERMAL_EMUL */
+
 /* CPU Zone information */
 #define PANIC_ZONE      4
 #define WARN_ZONE       3
@@ -125,6 +134,7 @@ struct exynos_tmu_data {
 struct thermal_trip_point_conf {
        int trip_val[MAX_TRIP_COUNT];
        int trip_count;
+       u8 trigger_falling;
 };
 
 struct thermal_cooling_conf {
@@ -174,7 +184,8 @@ static int exynos_set_mode(struct thermal_zone_device *thermal,
 
        mutex_lock(&th_zone->therm_dev->lock);
 
-       if (mode == THERMAL_DEVICE_ENABLED)
+       if (mode == THERMAL_DEVICE_ENABLED &&
+               !th_zone->sensor_conf->trip_data.trigger_falling)
                th_zone->therm_dev->polling_delay = IDLE_INTERVAL;
        else
                th_zone->therm_dev->polling_delay = 0;
@@ -284,7 +295,7 @@ static int exynos_bind(struct thermal_zone_device *thermal,
                case MONITOR_ZONE:
                case WARN_ZONE:
                        if (thermal_zone_bind_cooling_device(thermal, i, cdev,
-                                                               level, level)) {
+                                                               level, 0)) {
                                pr_err("error binding cdev inst %d\n", i);
                                ret = -EINVAL;
                        }
@@ -362,10 +373,17 @@ static int exynos_get_temp(struct thermal_zone_device *thermal,
 static int exynos_get_trend(struct thermal_zone_device *thermal,
                        int trip, enum thermal_trend *trend)
 {
-       if (thermal->temperature >= trip)
-               *trend = THERMAL_TREND_RAISING;
+       int ret;
+       unsigned long trip_temp;
+
+       ret = exynos_get_trip_temp(thermal, trip, &trip_temp);
+       if (ret < 0)
+               return ret;
+
+       if (thermal->temperature >= trip_temp)
+               *trend = THERMAL_TREND_RAISE_FULL;
        else
-               *trend = THERMAL_TREND_DROPPING;
+               *trend = THERMAL_TREND_DROP_FULL;
 
        return 0;
 }
@@ -413,7 +431,8 @@ static void exynos_report_trigger(void)
                        break;
        }
 
-       if (th_zone->mode == THERMAL_DEVICE_ENABLED) {
+       if (th_zone->mode == THERMAL_DEVICE_ENABLED &&
+               !th_zone->sensor_conf->trip_data.trigger_falling) {
                if (i > 0)
                        th_zone->therm_dev->polling_delay = ACTIVE_INTERVAL;
                else
@@ -452,7 +471,8 @@ static int exynos_register_thermal(struct thermal_sensor_conf *sensor_conf)
 
        th_zone->therm_dev = thermal_zone_device_register(sensor_conf->name,
                        EXYNOS_ZONE_COUNT, 0, NULL, &exynos_dev_ops, NULL, 0,
-                       IDLE_INTERVAL);
+                       sensor_conf->trip_data.trigger_falling ?
+                       0 : IDLE_INTERVAL);
 
        if (IS_ERR(th_zone->therm_dev)) {
                pr_err("Failed to register thermal zone device\n");
@@ -559,8 +579,9 @@ static int exynos_tmu_initialize(struct platform_device *pdev)
 {
        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
        struct exynos_tmu_platform_data *pdata = data->pdata;
-       unsigned int status, trim_info, rising_threshold;
-       int ret = 0, threshold_code;
+       unsigned int status, trim_info;
+       unsigned int rising_threshold = 0, falling_threshold = 0;
+       int ret = 0, threshold_code, i, trigger_levs = 0;
 
        mutex_lock(&data->lock);
        clk_enable(data->clk);
@@ -585,6 +606,11 @@ static int exynos_tmu_initialize(struct platform_device *pdev)
                        (data->temp_error2 != 0))
                data->temp_error1 = pdata->efuse_value;
 
+       /* Count trigger levels to be enabled */
+       for (i = 0; i < MAX_THRESHOLD_LEVS; i++)
+               if (pdata->trigger_levels[i])
+                       trigger_levs++;
+
        if (data->soc == SOC_ARCH_EXYNOS4210) {
                /* Write temperature code for threshold */
                threshold_code = temp_to_code(data, pdata->threshold);
@@ -594,44 +620,38 @@ static int exynos_tmu_initialize(struct platform_device *pdev)
                }
                writeb(threshold_code,
                        data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
-
-               writeb(pdata->trigger_levels[0],
-                       data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0);
-               writeb(pdata->trigger_levels[1],
-                       data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL1);
-               writeb(pdata->trigger_levels[2],
-                       data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL2);
-               writeb(pdata->trigger_levels[3],
-                       data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL3);
+               for (i = 0; i < trigger_levs; i++)
+                       writeb(pdata->trigger_levels[i],
+                       data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4);
 
                writel(EXYNOS4210_TMU_INTCLEAR_VAL,
                        data->base + EXYNOS_TMU_REG_INTCLEAR);
        } else if (data->soc == SOC_ARCH_EXYNOS) {
-               /* Write temperature code for threshold */
-               threshold_code = temp_to_code(data, pdata->trigger_levels[0]);
-               if (threshold_code < 0) {
-                       ret = threshold_code;
-                       goto out;
-               }
-               rising_threshold = threshold_code;
-               threshold_code = temp_to_code(data, pdata->trigger_levels[1]);
-               if (threshold_code < 0) {
-                       ret = threshold_code;
-                       goto out;
-               }
-               rising_threshold |= (threshold_code << 8);
-               threshold_code = temp_to_code(data, pdata->trigger_levels[2]);
-               if (threshold_code < 0) {
-                       ret = threshold_code;
-                       goto out;
+               /* Write temperature code for rising and falling threshold */
+               for (i = 0; i < trigger_levs; i++) {
+                       threshold_code = temp_to_code(data,
+                                               pdata->trigger_levels[i]);
+                       if (threshold_code < 0) {
+                               ret = threshold_code;
+                               goto out;
+                       }
+                       rising_threshold |= threshold_code << 8 * i;
+                       if (pdata->threshold_falling) {
+                               threshold_code = temp_to_code(data,
+                                               pdata->trigger_levels[i] -
+                                               pdata->threshold_falling);
+                               if (threshold_code > 0)
+                                       falling_threshold |=
+                                               threshold_code << 8 * i;
+                       }
                }
-               rising_threshold |= (threshold_code << 16);
 
                writel(rising_threshold,
                                data->base + EXYNOS_THD_TEMP_RISE);
-               writel(0, data->base + EXYNOS_THD_TEMP_FALL);
+               writel(falling_threshold,
+                               data->base + EXYNOS_THD_TEMP_FALL);
 
-               writel(EXYNOS_TMU_CLEAR_RISE_INT|EXYNOS_TMU_CLEAR_FALL_INT,
+               writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT,
                                data->base + EXYNOS_TMU_REG_INTCLEAR);
        }
 out:
@@ -664,6 +684,8 @@ static void exynos_tmu_control(struct platform_device *pdev, bool on)
                        pdata->trigger_level2_en << 8 |
                        pdata->trigger_level1_en << 4 |
                        pdata->trigger_level0_en;
+               if (pdata->threshold_falling)
+                       interrupt_en |= interrupt_en << 16;
        } else {
                con |= EXYNOS_TMU_CORE_OFF;
                interrupt_en = 0; /* Disable all interrupts */
@@ -697,20 +719,19 @@ static void exynos_tmu_work(struct work_struct *work)
        struct exynos_tmu_data *data = container_of(work,
                        struct exynos_tmu_data, irq_work);
 
+       exynos_report_trigger();
        mutex_lock(&data->lock);
        clk_enable(data->clk);
-
-
        if (data->soc == SOC_ARCH_EXYNOS)
-               writel(EXYNOS_TMU_CLEAR_RISE_INT,
+               writel(EXYNOS_TMU_CLEAR_RISE_INT |
+                               EXYNOS_TMU_CLEAR_FALL_INT,
                                data->base + EXYNOS_TMU_REG_INTCLEAR);
        else
                writel(EXYNOS4210_TMU_INTCLEAR_VAL,
                                data->base + EXYNOS_TMU_REG_INTCLEAR);
-
        clk_disable(data->clk);
        mutex_unlock(&data->lock);
-       exynos_report_trigger();
+
        enable_irq(data->irq);
 }
 
@@ -759,6 +780,7 @@ static struct exynos_tmu_platform_data const exynos4210_default_tmu_data = {
 
 #if defined(CONFIG_SOC_EXYNOS5250) || defined(CONFIG_SOC_EXYNOS4412)
 static struct exynos_tmu_platform_data const exynos_default_tmu_data = {
+       .threshold_falling = 10,
        .trigger_levels[0] = 85,
        .trigger_levels[1] = 103,
        .trigger_levels[2] = 110,
@@ -800,8 +822,6 @@ static const struct of_device_id exynos_tmu_match[] = {
        {},
 };
 MODULE_DEVICE_TABLE(of, exynos_tmu_match);
-#else
-#define  exynos_tmu_match NULL
 #endif
 
 static struct platform_device_id exynos_tmu_driver_ids[] = {
@@ -832,6 +852,94 @@ static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
        return (struct exynos_tmu_platform_data *)
                        platform_get_device_id(pdev)->driver_data;
 }
+
+#ifdef CONFIG_EXYNOS_THERMAL_EMUL
+static ssize_t exynos_tmu_emulation_show(struct device *dev,
+                                        struct device_attribute *attr,
+                                        char *buf)
+{
+       struct platform_device *pdev = container_of(dev,
+                                       struct platform_device, dev);
+       struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+       unsigned int reg;
+       u8 temp_code;
+       int temp = 0;
+
+       if (data->soc == SOC_ARCH_EXYNOS4210)
+               goto out;
+
+       mutex_lock(&data->lock);
+       clk_enable(data->clk);
+       reg = readl(data->base + EXYNOS_EMUL_CON);
+       clk_disable(data->clk);
+       mutex_unlock(&data->lock);
+
+       if (reg & EXYNOS_EMUL_ENABLE) {
+               reg >>= EXYNOS_EMUL_DATA_SHIFT;
+               temp_code = reg & EXYNOS_EMUL_DATA_MASK;
+               temp = code_to_temp(data, temp_code);
+       }
+out:
+       return sprintf(buf, "%d\n", temp * MCELSIUS);
+}
+
+static ssize_t exynos_tmu_emulation_store(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf, size_t count)
+{
+       struct platform_device *pdev = container_of(dev,
+                                       struct platform_device, dev);
+       struct exynos_tmu_data *data = platform_get_drvdata(pdev);
+       unsigned int reg;
+       int temp;
+
+       if (data->soc == SOC_ARCH_EXYNOS4210)
+               goto out;
+
+       if (!sscanf(buf, "%d\n", &temp) || temp < 0)
+               return -EINVAL;
+
+       mutex_lock(&data->lock);
+       clk_enable(data->clk);
+
+       reg = readl(data->base + EXYNOS_EMUL_CON);
+
+       if (temp) {
+               /* Both CELSIUS and MCELSIUS type are available for input */
+               if (temp > MCELSIUS)
+                       temp /= MCELSIUS;
+
+               reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) |
+                       (temp_to_code(data, (temp / MCELSIUS))
+                        << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE;
+       } else {
+               reg &= ~EXYNOS_EMUL_ENABLE;
+       }
+
+       writel(reg, data->base + EXYNOS_EMUL_CON);
+
+       clk_disable(data->clk);
+       mutex_unlock(&data->lock);
+
+out:
+       return count;
+}
+
+static DEVICE_ATTR(emulation, 0644, exynos_tmu_emulation_show,
+                                       exynos_tmu_emulation_store);
+static int create_emulation_sysfs(struct device *dev)
+{
+       return device_create_file(dev, &dev_attr_emulation);
+}
+static void remove_emulation_sysfs(struct device *dev)
+{
+       device_remove_file(dev, &dev_attr_emulation);
+}
+#else
+static inline int create_emulation_sysfs(struct device *dev) { return 0; }
+static inline void remove_emulation_sysfs(struct device *dev) {}
+#endif
+
 static int exynos_tmu_probe(struct platform_device *pdev)
 {
        struct exynos_tmu_data *data;
@@ -914,6 +1022,8 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                exynos_sensor_conf.trip_data.trip_val[i] =
                        pdata->threshold + pdata->trigger_levels[i];
 
+       exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling;
+
        exynos_sensor_conf.cooling_data.freq_clip_count =
                                                pdata->freq_tab_count;
        for (i = 0; i < pdata->freq_tab_count; i++) {
@@ -928,6 +1038,11 @@ static int exynos_tmu_probe(struct platform_device *pdev)
                dev_err(&pdev->dev, "Failed to register thermal interface\n");
                goto err_clk;
        }
+
+       ret = create_emulation_sysfs(&pdev->dev);
+       if (ret)
+               dev_err(&pdev->dev, "Failed to create emulation mode sysfs node\n");
+
        return 0;
 err_clk:
        platform_set_drvdata(pdev, NULL);
@@ -939,6 +1054,8 @@ static int exynos_tmu_remove(struct platform_device *pdev)
 {
        struct exynos_tmu_data *data = platform_get_drvdata(pdev);
 
+       remove_emulation_sysfs(&pdev->dev);
+
        exynos_tmu_control(pdev, false);
 
        exynos_unregister_thermal();
@@ -980,7 +1097,7 @@ static struct platform_driver exynos_tmu_driver = {
                .name   = "exynos-tmu",
                .owner  = THIS_MODULE,
                .pm     = EXYNOS_TMU_PM,
-               .of_match_table = exynos_tmu_match,
+               .of_match_table = of_match_ptr(exynos_tmu_match),
        },
        .probe = exynos_tmu_probe,
        .remove = exynos_tmu_remove,