From patchwork Wed Mar 25 07:50:24 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Xinwei Kong X-Patchwork-Id: 6089061 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id B3382BF90F for ; Wed, 25 Mar 2015 07:53:31 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 440AB2037D for ; Wed, 25 Mar 2015 07:53:30 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 8452720384 for ; Wed, 25 Mar 2015 07:53:28 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yag66-0005HQ-CQ; Wed, 25 Mar 2015 07:51:30 +0000 Received: from szxga01-in.huawei.com ([58.251.152.64]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1Yag5V-0004lk-4p for linux-arm-kernel@lists.infradead.org; Wed, 25 Mar 2015 07:50:56 +0000 Received: from 172.24.2.119 (EHLO SZXEML429-HUB.china.huawei.com) ([172.24.2.119]) by szxrg01-dlp.huawei.com (MOS 4.3.7-GA FastPath queued) with ESMTP id CLG84811; Wed, 25 Mar 2015 15:50:00 +0800 (CST) Received: from localhost (10.46.109.98) by SZXEML429-HUB.china.huawei.com (10.82.67.184) with Microsoft SMTP Server id 14.3.158.1; Wed, 25 Mar 2015 15:49:32 +0800 From: Xinwei Kong To: , , Subject: [PATCH v1 1/2] thermal: hisilicon: add new hisilicon thermal sensor driver Date: Wed, 25 Mar 2015 15:50:24 +0800 Message-ID: <1427269825-11324-2-git-send-email-kong.kongxinwei@hisilicon.com> X-Mailer: git-send-email 1.9.4.msysgit.2 In-Reply-To: <1427269825-11324-1-git-send-email-kong.kongxinwei@hisilicon.com> References: <1427269825-11324-1-git-send-email-kong.kongxinwei@hisilicon.com> MIME-Version: 1.0 X-Originating-IP: [10.46.109.98] X-CFilter-Loop: Reflected X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20150325_005053_902423_A0F67026 X-CRM114-Status: GOOD ( 22.13 ) X-Spam-Score: -0.0 (/) Cc: devicetree@vger.kernel.org, linux-pm@vger.kernel.org, linuxarm@huawei.com, xuwei5@hisilicon.com, linux-kernel@vger.kernel.org, liguozhu@hisilicon.com, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: kongxinwei This patch adds the support for hisilicon thermal sensor, within hisilicon SoC. there will register sensors for thermal framework and use device tree to bind cooling device. Signed-off-by: Leo Yan Signed-off-by: kongxinwei --- drivers/thermal/Kconfig | 8 + drivers/thermal/Makefile | 1 + drivers/thermal/hisi_thermal.c | 526 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 535 insertions(+) create mode 100644 drivers/thermal/hisi_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index af40db0..81aee01 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -136,6 +136,14 @@ config THERMAL_EMULATION because userland can easily disable the thermal policy by simply flooding this sysfs node with low temperature values. +config HISI_THERMAL + tristate "Hisilicon thermal driver" + depends on ARCH_HISI && CPU_THERMAL && OF + help + Enable this to plug hisilicon's thermal sensor driver into the Linux + thermal framework. cpufreq is used as the cooling device to throttle + CPUs when the passive trip is crossed. + config IMX_THERMAL tristate "Temperature sensor driver for Freescale i.MX SoCs" depends on CPU_THERMAL diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index fa0dc48..08ae7ac 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -39,3 +39,4 @@ obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/ obj-$(CONFIG_ST_THERMAL) += st/ obj-$(CONFIG_TEGRA_SOCTHERM) += tegra_soctherm.o +obj-$(CONFIG_HISI_THERMAL) += hisi_thermal.o diff --git a/drivers/thermal/hisi_thermal.c b/drivers/thermal/hisi_thermal.c new file mode 100644 index 0000000..8edf83a --- /dev/null +++ b/drivers/thermal/hisi_thermal.c @@ -0,0 +1,526 @@ +/* + * Hisilicon thermal sensor driver + * + * Copyright (c) 2014-2015 Hisilicon Limited. + * Copyright (c) 2014-2015 Linaro Limited. + * + * Xinwei Kong + * Leo Yan + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "thermal_core.h" + +#define TEMP0_LAG (0x0) +#define TEMP0_TH (0x4) +#define TEMP0_RST_TH (0x8) +#define TEMP0_CFG (0xC) +#define TEMP0_EN (0x10) +#define TEMP0_INT_EN (0x14) +#define TEMP0_INT_CLR (0x18) +#define TEMP0_RST_MSK (0x1C) +#define TEMP0_RAW_INT (0x20) +#define TEMP0_MSK_INT (0x24) +#define TEMP0_VALUE (0x28) + +#define HISI_TEMP_BASE (-60) +#define HISI_TEMP_RESET (100000) +#define HISI_TEMP_PASSIVE (85000) + +#define HISI_MAX_SENSORS 4 + +struct hisi_thermal_sensor { + struct hisi_thermal_data *thermal; + struct thermal_zone_device *tzd; + const struct thermal_trip *trip; + + uint32_t id; + uint32_t thres_temp, reset_temp; +}; + +struct hisi_thermal_data { + struct platform_device *pdev; + struct clk *clk; + + int irq, irq_bind_sensor; + bool irq_enabled; + + unsigned int sensors_num; + long sensor_temp[HISI_MAX_SENSORS]; + struct hisi_thermal_sensor sensors[HISI_MAX_SENSORS]; + + void __iomem *regs; +}; + +static DEFINE_SPINLOCK(thermal_lock); + +/* in millicelsius */ +static inline int _step_to_temp(int step) +{ + /* + * Every step equals (1 * 200) / 255 celsius, and finally + * need convert to millicelsius. + */ + return (HISI_TEMP_BASE + (step * 200 / 255)) * 1000; +} + +static inline int _temp_to_step(int temp) +{ + return ((temp / 1000 - HISI_TEMP_BASE) * 255 / 200); +} + +static long hisi_thermal_get_sensor_temp(struct hisi_thermal_data *data, + struct hisi_thermal_sensor *sensor) +{ + unsigned long flags; + int val; + + spin_lock_irqsave(&thermal_lock, flags); + + /* disable module firstly */ + writel(0x0, data->regs + TEMP0_EN); + + /* select sensor id */ + writel((sensor->id << 12), data->regs + TEMP0_CFG); + + /* enable module */ + writel(0x1, data->regs + TEMP0_EN); + + mdelay(5); + + val = readl(data->regs + TEMP0_VALUE); + val = _step_to_temp(val); + + /* adjust for negative value */ + val = (val < 0) ? 0 : val; + + spin_unlock_irqrestore(&thermal_lock, flags); + + return val; +} + +static void hisi_thermal_bind_irq(struct hisi_thermal_data *data) +{ + struct hisi_thermal_sensor *sensor; + unsigned long flags; + + sensor = &data->sensors[data->irq_bind_sensor]; + + spin_lock_irqsave(&thermal_lock, flags); + + /* setting the hdak time */ + writel(0x0, data->regs + TEMP0_CFG); + + /* disable module firstly */ + writel(0x0, data->regs + TEMP0_RST_MSK); + writel(0x0, data->regs + TEMP0_EN); + + /* select sensor id */ + writel((sensor->id << 12), data->regs + TEMP0_CFG); + + /* enable for interrupt */ + writel(_temp_to_step(sensor->thres_temp) + | 0x0FFFFFF00, data->regs + TEMP0_TH); + + writel(_temp_to_step(HISI_TEMP_RESET), data->regs + TEMP0_RST_TH); + + /* enable module */ + writel(0x1, data->regs + TEMP0_RST_MSK); + writel(0x1, data->regs + TEMP0_EN); + + writel(0x0, data->regs + TEMP0_INT_CLR); + writel(0x1, data->regs + TEMP0_INT_EN); + + mdelay(5); + + spin_unlock_irqrestore(&thermal_lock, flags); + +} + +static void hisi_thermal_enable_sensor(struct hisi_thermal_data *data) +{ + struct hisi_thermal_sensor *sensor; + unsigned long flags; + + sensor = &data->sensors[data->irq_bind_sensor]; + + spin_lock_irqsave(&thermal_lock, flags); + + /* setting the hdak time */ + writel(0x0, data->regs + TEMP0_CFG); + + /* disable module firstly */ + writel(0x0, data->regs + TEMP0_RST_MSK); + writel(0x0, data->regs + TEMP0_EN); + + /* select sensor id */ + writel((sensor->id << 12), data->regs + TEMP0_CFG); + + /* enable for interrupt */ + writel(_temp_to_step(sensor->thres_temp) + | 0x0FFFFFF00, data->regs + TEMP0_TH); + + writel(_temp_to_step(HISI_TEMP_RESET), data->regs + TEMP0_RST_TH); + + /* enable module */ + writel(0x1, data->regs + TEMP0_RST_MSK); + writel(0x1, data->regs + TEMP0_EN); + + writel(0x0, data->regs + TEMP0_INT_CLR); + writel(0x1, data->regs + TEMP0_INT_EN); + + mdelay(5); + + spin_unlock_irqrestore(&thermal_lock, flags); + +} + +static void hisi_thermal_disable_sensor(struct hisi_thermal_data *data) +{ + unsigned long flags; + + spin_lock_irqsave(&thermal_lock, flags); + + /* disable sensor module */ + writel(0x0, data->regs + TEMP0_INT_EN); + writel(0x0, data->regs + TEMP0_RST_MSK); + writel(0x0, data->regs + TEMP0_EN); + + spin_unlock_irqrestore(&thermal_lock, flags); + +} + +static int hisi_thermal_get_temp(void *_sensor, long *temp) +{ + struct hisi_thermal_sensor *sensor = _sensor; + struct hisi_thermal_data *data = sensor->thermal; + + int sensor_id = 0, i; + long max_temp = 0; + + *temp = hisi_thermal_get_sensor_temp(data, sensor); + + data->sensor_temp[sensor->id] = *temp; + + for (i = 0; i < HISI_MAX_SENSORS; i++) { + if (data->sensor_temp[i] >= max_temp) { + max_temp = data->sensor_temp[i]; + sensor_id = i; + } + } + + data->irq_bind_sensor = sensor_id; + + dev_dbg(&data->pdev->dev, "id=%d, irq=%d, temp=%ld, thres=%d\n", + sensor->id, data->irq_enabled, *temp, sensor->thres_temp); + + /* + * Bind irq to sensor for two cases: + * Reenable alarm IRQ if temperature below threshold; + * if irq has been enabled, always set it; + */ + if (!data->irq_enabled && max_temp < sensor->thres_temp) { + data->irq_enabled = true; + hisi_thermal_bind_irq(data); + } else if (data->irq_enabled) + hisi_thermal_bind_irq(data); + + return 0; +} + +static struct thermal_zone_of_device_ops hisi_of_thermal_ops = { + .get_temp = hisi_thermal_get_temp, +}; + +static irqreturn_t hisi_thermal_alarm_irq(int irq, void *dev) +{ + struct hisi_thermal_data *data = dev; + unsigned long flags; + + spin_lock_irqsave(&thermal_lock, flags); + + /* disable interrupt */ + writel(0x0, data->regs + TEMP0_INT_EN); + writel(0x1, data->regs + TEMP0_INT_CLR); + + spin_unlock_irqrestore(&thermal_lock, flags); + data->irq_enabled = false; + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t hisi_thermal_alarm_irq_thread(int irq, void *dev) +{ + struct hisi_thermal_data *data = dev; + struct hisi_thermal_sensor *sensor; + int i; + + sensor = &data->sensors[data->irq_bind_sensor]; + + dev_crit(&data->pdev->dev, "THERMAL ALARM: T > %d\n", + sensor->thres_temp / 1000); + + for (i = 0; i < data->sensors_num; i++) + thermal_zone_device_update(data->sensors[i].tzd); + + return IRQ_HANDLED; +} + +static int hisi_thermal_init_sensor(struct platform_device *pdev, + struct device_node *np, + struct hisi_thermal_data *data, + int index) +{ + struct hisi_thermal_sensor *sensor; + int ret, i; + + sensor = &data->sensors[index]; + + ret = of_property_read_u32(np, "hisilicon,tsensor-id", + &sensor->id); + if (ret) { + dev_err(&pdev->dev, "failed to get id %d: %d\n", index, ret); + return ret; + } + + sensor->thermal = data; + sensor->tzd = thermal_zone_of_sensor_register(&pdev->dev, sensor->id, + sensor, &hisi_of_thermal_ops); + if (IS_ERR(sensor->tzd)) { + ret = PTR_ERR(sensor->tzd); + dev_err(&pdev->dev, "failed to register sensor id %d: %d\n", + sensor->id, ret); + return ret; + } + + sensor->trip = of_thermal_get_trip_points(sensor->tzd); + + for (i = 0; i < of_thermal_get_ntrips(sensor->tzd); i++) { + if (sensor->trip[i].type == THERMAL_TRIP_PASSIVE) { + sensor->thres_temp = sensor->trip[i].temperature; + break; + } + } + + return 0; +} + +static int hisi_thermal_get_sensor_data(struct platform_device *pdev) +{ + struct hisi_thermal_data *data = platform_get_drvdata(pdev); + struct device_node *np = pdev->dev.of_node; + struct device_node *child_np; + int ret, i, index; + + /* sensor initialization */ + index = 0; + for_each_child_of_node(np, child_np) { + + if (index >= HISI_MAX_SENSORS) { + dev_err(&pdev->dev, "unsupported sensor number\n"); + ret = -EINVAL; + goto err_init_sensors; + } + + ret = hisi_thermal_init_sensor(pdev, child_np, data, index); + if (ret) + goto err_init_sensors; + + index++; + } + + data->sensors_num = index; + return 0; + +err_init_sensors: + for (i = 0; i < index; i++) + thermal_zone_of_sensor_unregister(&pdev->dev, + data->sensors[i].tzd); + return ret; +} + +static const struct of_device_id of_hisi_thermal_match[] = { + { .compatible = "hisilicon,tsensor" }, + { /* end */ } +}; +MODULE_DEVICE_TABLE(of, of_hisi_thermal_match); + +static void hisi_thermal_toggle_sensor(struct hisi_thermal_sensor *sensor, + bool on) +{ + struct thermal_zone_device *tzd = sensor->tzd; + + tzd->ops->set_mode(tzd, + on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED); +} + +static int hisi_thermal_probe(struct platform_device *pdev) +{ + struct hisi_thermal_data *data; + struct resource *res; + int i; + int ret; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + data->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(data->regs)) { + dev_err(&pdev->dev, "failed to get io address\n"); + return PTR_ERR(data->regs); + } + + data->irq = platform_get_irq(pdev, 0); + if (data->irq < 0) + return data->irq; + + ret = devm_request_threaded_irq(&pdev->dev, data->irq, + hisi_thermal_alarm_irq, hisi_thermal_alarm_irq_thread, + 0, "hisi_thermal", data); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request alarm irq: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, data); + + data->clk = devm_clk_get(&pdev->dev, "thermal_clk"); + if (IS_ERR(data->clk)) { + ret = PTR_ERR(data->clk); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "failed to get thermal clk: %d\n", ret); + return ret; + } + + /* enable clock for thermal */ + ret = clk_prepare_enable(data->clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable thermal clk: %d\n", ret); + return ret; + } + + ret = hisi_thermal_get_sensor_data(pdev); + if (ret) { + dev_err(&pdev->dev, "failed to get sensor data\n"); + goto err_get_sensor_data; + return ret; + } + + hisi_thermal_enable_sensor(data); + data->irq_enabled = true; + + for (i = 0; i < data->sensors_num; i++) + hisi_thermal_toggle_sensor(&data->sensors[i], true); + + return 0; + +err_get_sensor_data: + clk_disable_unprepare(data->clk); + return ret; +} + +static int hisi_thermal_remove(struct platform_device *pdev) +{ + struct hisi_thermal_data *data = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < data->sensors_num; i++) { + struct hisi_thermal_sensor *sensor = &data->sensors[i]; + + hisi_thermal_toggle_sensor(sensor, false); + thermal_zone_of_sensor_unregister(&pdev->dev, sensor->tzd); + } + + hisi_thermal_disable_sensor(data); + clk_disable_unprepare(data->clk); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int hisi_thermal_suspend(struct device *dev) +{ + struct hisi_thermal_data *data = dev_get_drvdata(dev); + + hisi_thermal_disable_sensor(data); + data->irq_enabled = false; + + clk_disable_unprepare(data->clk); + + return 0; +} + +static int hisi_thermal_resume(struct device *dev) +{ + struct hisi_thermal_data *data = dev_get_drvdata(dev); + + clk_prepare_enable(data->clk); + + data->irq_enabled = true; + hisi_thermal_enable_sensor(data); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(hisi_thermal_pm_ops, + hisi_thermal_suspend, hisi_thermal_resume); + +static struct platform_driver hisi_thermal_driver = { + .driver = { + .name = "hisi_thermal", + .owner = THIS_MODULE, + .pm = &hisi_thermal_pm_ops, + .of_match_table = of_hisi_thermal_match, + }, + .probe = hisi_thermal_probe, + .remove = hisi_thermal_remove, +}; + +static int __init hisi_thermal_init(void) +{ + return platform_driver_register(&hisi_thermal_driver); +} + +static void __exit hisi_thermal_exit(void) +{ + platform_driver_unregister(&hisi_thermal_driver); +} + +late_initcall(hisi_thermal_init); +module_exit(hisi_thermal_exit); + +MODULE_AUTHOR("Xinwei Kong "); +MODULE_AUTHOR("Leo Yan "); +MODULE_DESCRIPTION("Hisilicon thermal driver"); +MODULE_LICENSE("GPL v2");