From patchwork Thu Jun 5 15:06:55 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lee Jones X-Patchwork-Id: 4306541 X-Patchwork-Delegate: rui.zhang@intel.com Return-Path: X-Original-To: patchwork-linux-pm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id 00908BEEA7 for ; Thu, 5 Jun 2014 15:10:35 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 98C7E20274 for ; Thu, 5 Jun 2014 15:10:31 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 23955201EC for ; Thu, 5 Jun 2014 15:10:29 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752191AbaFEPHo (ORCPT ); Thu, 5 Jun 2014 11:07:44 -0400 Received: from mail-ig0-f176.google.com ([209.85.213.176]:41783 "EHLO mail-ig0-f176.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752189AbaFEPHm (ORCPT ); Thu, 5 Jun 2014 11:07:42 -0400 Received: by mail-ig0-f176.google.com with SMTP id a13so3322616igq.3 for ; Thu, 05 Jun 2014 08:07:41 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=1TvfUE8Tv+nY3oQvEwNT3baFerC62wlj+16BYagA7mY=; b=lGBdFMIGJEY8Qp4CVpad9e2kN9aOOC4RzUyhrilwbjr7GWALDHiQIJ/0kOdfS9ageS qS7qRGobCVchRrXSuwPXmT3iJBVJ4BshMli3fWOX8Vk0TDsu+LapN9ay1ilCdOXA6Q1h lHeydwnz1Evoy7/i1rfLdoTAiN8DKyOCk0X7l65FfL+8J2zzeaqe3I56fS9FoBWOwGap Xsnq21WlSyPvRAVKCSdNJEjCBB0ujxG/BAcZULIRfcprUVCIvMrsS96J7C6H680EgbPr TexG3Aw9EodlF3crPy5CZG7gkQOWr2C1xzup/UJmaONaWIipf1WeMgZnRFPVxgZqv3f/ ns8g== X-Gm-Message-State: ALoCoQkGoNmIxUUFXjgYDpwuEYvE4kehjd0VyMOoseDq2IpuNr2b6xloIWu7cKWlwB7mHDTdE+1l X-Received: by 10.50.20.33 with SMTP id k1mr21158707ige.26.1401980861433; Thu, 05 Jun 2014 08:07:41 -0700 (PDT) Received: from localhost.localdomain (host86-181-29-77.range86-181.btcentralplus.com. [86.181.29.77]) by mx.google.com with ESMTPSA id 2sm19108949igs.17.2014.06.05.08.07.38 for (version=TLSv1.1 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Thu, 05 Jun 2014 08:07:40 -0700 (PDT) From: Lee Jones To: linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org Cc: lee.jones@linaro.org, kernel@stlinux.com, ajitpal.singh@st.com, edubezval@gmail.com, rui.zhang@intel.com, linux-pm@vger.kernel.org, devicetree@vger.kernel.org, maxime.coquelin@st.com, patrice.chotard@st.com Subject: [PATCH 2/6] thermal: sti: Introduce ST Thermal core code Date: Thu, 5 Jun 2014 16:06:55 +0100 Message-Id: <1401980819-19962-3-git-send-email-lee.jones@linaro.org> X-Mailer: git-send-email 1.8.3.2 In-Reply-To: <1401980819-19962-1-git-send-email-lee.jones@linaro.org> References: <1401980819-19962-1-git-send-email-lee.jones@linaro.org> Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Spam-Status: No, score=-7.5 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, 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 This core is shared by both ST's 'memory mapped' and 'system configuration register' based Thermal controllers. Signed-off-by: Ajit Pal Singh Signed-off-by: Lee Jones Acked-by: Pavel Machek --- drivers/thermal/Kconfig | 5 + drivers/thermal/Makefile | 1 + drivers/thermal/st/Kconfig | 4 + drivers/thermal/st/Makefile | 1 + drivers/thermal/st/st_thermal.c | 313 ++++++++++++++++++++++++++++++++++++++++ drivers/thermal/st/st_thermal.h | 104 +++++++++++++ 6 files changed, 428 insertions(+) create mode 100644 drivers/thermal/st/Kconfig create mode 100644 drivers/thermal/st/Makefile create mode 100644 drivers/thermal/st/st_thermal.c create mode 100644 drivers/thermal/st/st_thermal.h diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 2d51912..6f88de1 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -231,4 +231,9 @@ depends on PLAT_SAMSUNG source "drivers/thermal/samsung/Kconfig" endmenu +menu "STMicroelectronics thermal drivers" +depends on ARCH_STI && OF +source "drivers/thermal/st/Kconfig" +endmenu + endif diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 54e4ec9..ea36799 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -31,3 +31,4 @@ obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/ obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o +obj-$(CONFIG_ST_THERMAL) += st/ diff --git a/drivers/thermal/st/Kconfig b/drivers/thermal/st/Kconfig new file mode 100644 index 0000000..1ba6760 --- /dev/null +++ b/drivers/thermal/st/Kconfig @@ -0,0 +1,4 @@ +config ST_THERMAL + tristate "Thermal sensors on STMicroelectronics STi series of SoCs" + help + Support for thermal sensors on STMicroelectronics STi series of SoCs. diff --git a/drivers/thermal/st/Makefile b/drivers/thermal/st/Makefile new file mode 100644 index 0000000..10f4bf6 --- /dev/null +++ b/drivers/thermal/st/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_ST_THERMAL) := st_thermal.o diff --git a/drivers/thermal/st/st_thermal.c b/drivers/thermal/st/st_thermal.c new file mode 100644 index 0000000..90163b3 --- /dev/null +++ b/drivers/thermal/st/st_thermal.c @@ -0,0 +1,313 @@ +/* + * ST Thermal Sensor Driver core routines + * Author: Ajit Pal Singh + * + * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited + * + * 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; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include +#include +#include +#include + +#include "st_thermal.h" + +/* The Thermal Framework expects millidegrees */ +#define mcelsius(temp) ((temp) * 1000) + +/* + * Function to allocate regfields which are common + * between syscfg and memory mapped based sensors + */ +int st_thermal_alloc_regfields(struct st_thermal_sensor *sensor) +{ + struct device *dev = sensor->dev; + struct regmap *regmap = sensor->regmap; + const struct reg_field *reg_fields = sensor->cdata->reg_fields; + + sensor->dcorrect = devm_regmap_field_alloc(dev, regmap, + reg_fields[DCORRECT]); + + sensor->overflow = devm_regmap_field_alloc(dev, regmap, + reg_fields[OVERFLOW]); + + sensor->temp_data = devm_regmap_field_alloc(dev, regmap, + reg_fields[DATA]); + + if (IS_ERR(sensor->dcorrect) || + IS_ERR(sensor->overflow) || + IS_ERR(sensor->temp_data)) { + dev_err(dev, "failed to allocate common regfields\n"); + return -EINVAL; + } + + return sensor->ops->alloc_regfields(sensor); +} + +static int st_thermal_sensor_on(struct st_thermal_sensor *sensor) +{ + int ret; + struct device *dev = sensor->dev; + + ret = clk_prepare_enable(sensor->clk); + if (ret) { + dev_err(dev, "failed to enable clk\n"); + return ret; + } + + ret = sensor->ops->power_ctrl(sensor, POWER_ON); + if (ret) { + dev_err(dev, "failed to power on sensor\n"); + clk_disable_unprepare(sensor->clk); + } + + return ret; +} + +static int st_thermal_sensor_off(struct st_thermal_sensor *sensor) +{ + int ret; + + ret = sensor->ops->power_ctrl(sensor, POWER_OFF); + if (ret) + return ret; + + clk_disable_unprepare(sensor->clk); + + return 0; +} + +static int st_thermal_calibration(struct st_thermal_sensor *sensor) +{ + int ret; + unsigned int val; + struct device *dev = sensor->dev; + + /* Check if sensor calibration data is already written */ + ret = regmap_field_read(sensor->dcorrect, &val); + if (ret) { + dev_err(dev, "failed to read calibration data\n"); + return ret; + } + + if (!val) { + /* + * Sensor calibration value not set by bootloader, + * default calibration data to be used + */ + ret = regmap_field_write(sensor->dcorrect, + sensor->cdata->calibration_val); + if (ret) + dev_err(dev, "failed to set calibration data\n"); + } + + return ret; +} + +/* Callback to get temperature from HW*/ +static int st_thermal_get_temp(struct thermal_zone_device *th, + unsigned long *temperature) +{ + struct st_thermal_sensor *sensor = th->devdata; + struct device *dev = sensor->dev; + unsigned int temp; + unsigned int overflow; + int ret; + + ret = regmap_field_read(sensor->overflow, &overflow); + if (ret) + return ret; + if (overflow) + return -EIO; + + ret = regmap_field_read(sensor->temp_data, &temp); + if (ret) + return ret; + + temp += sensor->cdata->temp_adjust_val; + temp = mcelsius(temp); + + dev_dbg(dev, "temperature: %d\n", temp); + + *temperature = temp; + + return 0; +} + +static int st_thermal_get_trip_type(struct thermal_zone_device *th, + int trip, enum thermal_trip_type *type) +{ + struct st_thermal_sensor *sensor = th->devdata; + struct device *dev = sensor->dev; + + switch (trip) { + case 0: + *type = THERMAL_TRIP_CRITICAL; + break; + default: + dev_err(dev, "invalid trip point\n"); + return -EINVAL; + } + + return 0; +} + +static int st_thermal_get_trip_temp(struct thermal_zone_device *th, + int trip, unsigned long *temp) +{ + struct st_thermal_sensor *sensor = th->devdata; + struct device *dev = sensor->dev; + + switch (trip) { + case 0: + *temp = mcelsius(sensor->cdata->crit_temp); + break; + default: + dev_err(dev, "Invalid trip point\n"); + return -EINVAL; + } + + return 0; +} + +static struct thermal_zone_device_ops st_tz_ops = { + .get_temp = st_thermal_get_temp, + .get_trip_type = st_thermal_get_trip_type, + .get_trip_temp = st_thermal_get_trip_temp, +}; + +int st_thermal_register(struct platform_device *pdev, + const struct of_device_id *st_thermal_of_match) +{ + struct st_thermal_sensor *sensor; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + const struct of_device_id *match; + + int polling_delay; + int ret; + + if (!np) { + dev_err(dev, "device tree node not found\n"); + return -EINVAL; + } + + sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) + return -ENOMEM; + + sensor->dev = dev; + + match = of_match_device(st_thermal_of_match, dev); + if (!(match && match->data)) + return -EINVAL; + + sensor->cdata = match->data; + if (!sensor->cdata->ops) + return -EINVAL; + + sensor->ops = sensor->cdata->ops; + + ret = sensor->ops->regmap_init(sensor); + if (ret) + return ret; + + ret = st_thermal_alloc_regfields(sensor); + if (ret) + return ret; + + sensor->clk = devm_clk_get(dev, "thermal"); + if (IS_ERR(sensor->clk)) { + dev_err(dev, "failed to fetch clock\n"); + return PTR_ERR(sensor->clk); + } + + if (sensor->ops->register_enable_irq) { + ret = sensor->ops->register_enable_irq(sensor); + if (ret) + return ret; + } + + ret = st_thermal_sensor_on(sensor); + if (ret) + return ret; + + ret = st_thermal_calibration(sensor); + if (ret) + goto sensor_off; + + polling_delay = sensor->ops->register_enable_irq ? 0 : 1000; + + sensor->thermal_dev = + thermal_zone_device_register(dev_name(dev), 1, 0, sensor, + &st_tz_ops, NULL, 0, polling_delay); + if (IS_ERR(sensor->thermal_dev)) { + dev_err(dev, "failed to register thermal zone device\n"); + ret = PTR_ERR(sensor->thermal_dev); + goto sensor_off; + } + + platform_set_drvdata(pdev, sensor); + + return 0; + +sensor_off: + st_thermal_sensor_off(sensor); + + return ret; +} +EXPORT_SYMBOL_GPL(st_thermal_register); + +int st_thermal_unregister(struct platform_device *pdev) +{ + struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); + + st_thermal_sensor_off(sensor); + thermal_zone_device_unregister(sensor->thermal_dev); + + return 0; +} +EXPORT_SYMBOL_GPL(st_thermal_unregister); + +static int st_thermal_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); + + return st_thermal_sensor_off(sensor); +} + +static int st_thermal_resume(struct device *dev) +{ + int ret; + struct platform_device *pdev = to_platform_device(dev); + struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); + + ret = st_thermal_sensor_on(sensor); + if (ret) + return ret; + + ret = st_thermal_calibration(sensor); + if (ret) + return ret; + + if (sensor->ops->enable_irq) { + ret = sensor->ops->enable_irq(sensor); + if (ret) + return ret; + } + + return 0; +} +SIMPLE_DEV_PM_OPS(st_thermal_pm_ops, st_thermal_suspend, st_thermal_resume); +EXPORT_SYMBOL_GPL(st_thermal_pm_ops); + +MODULE_AUTHOR("STMicroelectronics (R&D) Limited "); +MODULE_DESCRIPTION("STMicroelectronics STi SoC Thermal Sensor Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/thermal/st/st_thermal.h b/drivers/thermal/st/st_thermal.h new file mode 100644 index 0000000..fecafbe --- /dev/null +++ b/drivers/thermal/st/st_thermal.h @@ -0,0 +1,104 @@ +/* + * ST Thermal Sensor Driver for STi series of SoCs + * Author: Ajit Pal Singh + * + * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited + * + * 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; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __STI_THERMAL_SYSCFG_H +#define __STI_THERMAL_SYSCFG_H + +#include +#include +#include +#include + +enum st_thermal_regfield_ids { + INT_THRESH_HI = 0, /* Top two regfield IDs are mutually exclusive */ + TEMP_PWR = 0, + DCORRECT, + OVERFLOW, + DATA, + INT_ENABLE, + + MAX_REGFIELDS +}; + +/* Thermal sensor power states */ +enum st_thermal_power_state { + POWER_OFF = 0, + POWER_ON +}; + +struct st_thermal_sensor; + +/** + * Description of private thermal sensor ops. + * + * @power_ctrl: Function for powering on/off a sensor. Clock to the + * sensor is also controlled from this function. + * @alloc_regfields: Allocate regmap register fields, specific to a sensor. + * @do_memmap_regmap: Memory map the thermal register space and init regmap + * instance or find regmap instance. + * @register_irq: Register an interrupt handler for a sensor. + */ +struct st_thermal_sensor_ops { + int (*power_ctrl)(struct st_thermal_sensor *, enum st_thermal_power_state); + int (*alloc_regfields)(struct st_thermal_sensor *); + int (*regmap_init)(struct st_thermal_sensor *); + int (*register_enable_irq)(struct st_thermal_sensor *); + int (*enable_irq)(struct st_thermal_sensor *); +}; + +/** + * Description of thermal driver compatible data. + * + * @reg_fields: Pointer to the regfields array for a sensor. + * @sys_compat: Pointer to the syscon node compatible string. + * @ops: Pointer to private thermal ops for a sensor. + * @calibration_val: Default calibration value to be written to the DCORRECT + * register field for a sensor. + * @temp_adjust_val: Value to be added/subtracted from the data read from + * the sensor. If value needs to be added please provide a + * positive value and if it is to be subtracted please + * provide a negative value. + * @crit_temp: The temperature beyond which the SoC should be shutdown + * to prevent damage. + */ +struct st_thermal_compat_data { + char *sys_compat; + const struct reg_field *reg_fields; + const struct st_thermal_sensor_ops *ops; + unsigned int calibration_val; + int temp_adjust_val; + int crit_temp; +}; + +struct st_thermal_sensor { + struct device *dev; + struct thermal_zone_device *thermal_dev; + const struct st_thermal_sensor_ops *ops; + const struct st_thermal_compat_data *cdata; + struct clk *clk; + struct regmap *regmap; + struct regmap_field *pwr; + struct regmap_field *dcorrect; + struct regmap_field *overflow; + struct regmap_field *temp_data; + struct regmap_field *int_thresh_hi; + struct regmap_field *int_enable; + int irq; + void __iomem *mmio_base; +}; + +extern int st_thermal_register(struct platform_device *pdev, + const struct of_device_id *st_thermal_of_match); +extern int st_thermal_unregister(struct platform_device *pdev); +extern const struct dev_pm_ops st_thermal_pm_ops; + +#endif /* __STI_RESET_SYSCFG_H */