From patchwork Sun Jun 19 03:34:54 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: khiemnguyen X-Patchwork-Id: 9186019 X-Patchwork-Delegate: rui.zhang@intel.com Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id AA372601C0 for ; Sun, 19 Jun 2016 03:35:03 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 9EF4B25D99 for ; Sun, 19 Jun 2016 03:35:03 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 92E58262AE; Sun, 19 Jun 2016 03:35:03 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4A87025D99 for ; Sun, 19 Jun 2016 03:35:01 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751242AbcFSDe7 (ORCPT ); Sat, 18 Jun 2016 23:34:59 -0400 Received: from relmlor3.renesas.com ([210.160.252.173]:24600 "EHLO relmlie2.idc.renesas.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751186AbcFSDe6 (ORCPT ); Sat, 18 Jun 2016 23:34:58 -0400 Received: from unknown (HELO relmlir1.idc.renesas.com) ([10.200.68.151]) by relmlie2.idc.renesas.com with ESMTP; 19 Jun 2016 12:34:55 +0900 Received: from relmlac3.idc.renesas.com (relmlac3.idc.renesas.com [10.200.69.23]) by relmlir1.idc.renesas.com (Postfix) with ESMTP id EA14B484D6; Sun, 19 Jun 2016 12:34:55 +0900 (JST) Received: by relmlac3.idc.renesas.com (Postfix, from userid 0) id D9E3018070; Sun, 19 Jun 2016 12:34:55 +0900 (JST) Received: from relmlac3.idc.renesas.com (localhost [127.0.0.1]) by relmlac3.idc.renesas.com (Postfix) with ESMTP id D1CA21806F; Sun, 19 Jun 2016 12:34:55 +0900 (JST) Received: from relmlii2.idc.renesas.com [10.200.68.66] by relmlac3.idc.renesas.com with ESMTP id NAD19815; Sun, 19 Jun 2016 12:34:55 +0900 X-IronPort-AV: E=Sophos;i="5.22,559,1449500400"; d="scan'208";a="213737180" Received: from unknown (HELO outside-ironport.rvc.renesas.com) ([172.29.139.110]) by relmlii2.idc.renesas.com with ESMTP; 19 Jun 2016 12:34:55 +0900 Received: from rvc-hts-01.rvc.renesas.com ([172.29.139.122]) by inside-ironport.rvc.renesas.com with ESMTP; 19 Jun 2016 10:34:54 +0700 Received: from [172.29.157.15] (172.29.157.15) by rvc-hts-01.rvc.renesas.com (172.29.139.120) with Microsoft SMTP Server id 8.3.83.0; Sun, 19 Jun 2016 10:34:54 +0700 Subject: [PATCH 2/4] thermal: rcar_gen3_thermal: Add R-Car Gen3 thermal driver support To: Kuninori Morimoto References: <57661211.7010900@rvc.renesas.com> CC: Wolfram Sang , Geert Uytterhoeven , Magnus Damm , Zhang Rui , Eduardo Valentin , Rob Herring , Mark Rutland , "linux-pm@vger.kernel.org" , "devicetree@vger.kernel.org" , "linux-kernel@vger.kernel.org" , "Thao Phuong Le. Nguyen" , "Hien Duy. Dang" , Toru Oishi , "Khiem Trong. Nguyen" From: Khiem Nguyen Message-ID: <576612DE.3070204@rvc.renesas.com> Date: Sun, 19 Jun 2016 10:34:54 +0700 User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:38.0) Gecko/20100101 Thunderbird/38.2.0 MIME-Version: 1.0 In-Reply-To: <57661211.7010900@rvc.renesas.com> Sender: linux-pm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-pm@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Hien Dang Signed-off-by: Thao Nguyen Signed-off-by: Khiem Nguyen --- drivers/thermal/Kconfig | 9 + drivers/thermal/Makefile | 1 + drivers/thermal/rcar_gen3_thermal.c | 524 ++++++++++++++++++++++++++++++++++++ 3 files changed, 534 insertions(+) create mode 100644 drivers/thermal/rcar_gen3_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 2d702ca..151feb7 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -223,6 +223,15 @@ config RCAR_THERMAL Enable this to plug the R-Car thermal sensor driver into the Linux thermal framework. +config RCAR_GEN3_THERMAL + tristate "Renesas R-Car Gen3 thermal driver" + depends on ARCH_RENESAS || COMPILE_TEST + depends on HAS_IOMEM + depends on OF + help + Enable this to plug the R-Car Gen3 thermal sensor driver into the Linux + thermal framework. + config KIRKWOOD_THERMAL tristate "Temperature sensor on Marvell Kirkwood SoCs" depends on MACH_KIRKWOOD || COMPILE_TEST diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index cded802..3ac9186 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -31,6 +31,7 @@ obj-$(CONFIG_QCOM_SPMI_TEMP_ALARM) += qcom-spmi-temp-alarm.o obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o obj-$(CONFIG_ROCKCHIP_THERMAL) += rockchip_thermal.o obj-$(CONFIG_RCAR_THERMAL) += rcar_thermal.o +obj-$(CONFIG_RCAR_GEN3_THERMAL) += rcar_gen3_thermal.o obj-$(CONFIG_KIRKWOOD_THERMAL) += kirkwood_thermal.o obj-y += samsung/ obj-$(CONFIG_DOVE_THERMAL) += dove_thermal.o diff --git a/drivers/thermal/rcar_gen3_thermal.c b/drivers/thermal/rcar_gen3_thermal.c new file mode 100644 index 0000000..a9a372b --- /dev/null +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -0,0 +1,524 @@ +/* + * R-Car Gen3 THS/CIVM thermal sensor driver + * Based on drivers/thermal/rcar_thermal.c + * + * Copyright (C) 2016 Renesas Electronics Corporation. + * + * 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 of the License. + * + * 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. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Register offset */ +#define REG_GEN3_CTSR 0x20 +#define REG_GEN3_THCTR 0x20 +#define REG_GEN3_IRQSTR 0x04 +#define REG_GEN3_IRQMSK 0x08 +#define REG_GEN3_IRQCTL 0x0C +#define REG_GEN3_IRQEN 0x10 +#define REG_GEN3_IRQTEMP1 0x14 +#define REG_GEN3_IRQTEMP2 0x18 +#define REG_GEN3_IRQTEMP3 0x1C +#define REG_GEN3_TEMP 0x28 +#define REG_GEN3_THCODE1 0x50 +#define REG_GEN3_THCODE2 0x54 +#define REG_GEN3_THCODE3 0x58 + +#define PTAT_BASE 0xE6198000 +#define REG_GEN3_PTAT1 0x5C +#define REG_GEN3_PTAT2 0x60 +#define REG_GEN3_PTAT3 0x64 +#define PTAT_SIZE REG_GEN3_PTAT3 + +/* CTSR bit */ +#define PONM (0x1 << 8) +#define AOUT (0x1 << 7) +#define THBGR (0x1 << 5) +#define VMEN (0x1 << 4) +#define VMST (0x1 << 1) +#define THSST (0x1 << 0) + +/* THCTR bit */ +#define CTCTL (0x1 << 24) +#define THCNTSEN(x) (x << 16) + +#define BIT_LEN_12 0x1 + +#define CTEMP_MASK 0xFFF + +#define MCELSIUS(temp) ((temp) * 1000) +#define TEMP_IRQ_SHIFT(tsc_id) (0x1 << tsc_id) +#define TEMPD_IRQ_SHIFT(tsc_id) (0x1 << (tsc_id + 3)) +#define GEN3_FUSE_MASK 0xFFF + +/* Structure for thermal temperature calculation */ +struct equation_coefs { + long a1; + long b1; + long a2; + long b2; +}; + +struct fuse_factors { + int thcode_1; + int thcode_2; + int thcode_3; + int ptat_1; + int ptat_2; + int ptat_3; +}; + +struct rcar_gen3_thermal_priv { + void __iomem *base; + struct device *dev; + struct thermal_zone_device *zone; + struct delayed_work work; + struct fuse_factors factor; + struct equation_coefs coef; + spinlock_t lock; + int id; + int irq; + u32 ctemp; + const struct rcar_gen3_thermal_data *data; +}; + +struct rcar_gen3_thermal_data { + int (*thermal_init)(struct rcar_gen3_thermal_priv *priv); +}; + +#define rcar_priv_to_dev(priv) ((priv)->dev) +#define rcar_has_irq_support(priv) ((priv)->irq) + +/* Temperature calculation */ +#define CODETSD(x) ((x) * 1000) +#define TJ_1 96000L +#define TJ_3 (-41000L) +#define PW2(x) ((x)*(x)) + +static u32 thermal_reg_read(struct rcar_gen3_thermal_priv *priv, u32 reg) +{ + return ioread32(priv->base + reg); +} + +static void thermal_reg_write(struct rcar_gen3_thermal_priv *priv, + u32 reg, u32 data) +{ + iowrite32(data, priv->base + reg); +} + +static int _round_temp(int temp) +{ + int tmp1, tmp2; + int result = 0; + + tmp1 = abs(temp) % 1000; + tmp2 = abs(temp) / 1000; + + if (tmp1 < 250) + result = CODETSD(tmp2); + else if (tmp1 < 750 && tmp1 >= 250) + result = CODETSD(tmp2) + 500; + else + result = CODETSD(tmp2) + 1000; + + return ((temp < 0) ? (result * (-1)) : result); +} + +static int _read_fuse_factor(struct rcar_gen3_thermal_priv *priv) +{ + /* + * FIXME: The value should be read from some FUSE registers. + * For available SoC, these registers have not been supported yet. + * The pre-defined value will be applied for now. + */ + priv->factor.ptat_1 = 2351; + priv->factor.ptat_2 = 1509; + priv->factor.ptat_3 = 435; + switch (priv->id) { + case 0: + priv->factor.thcode_1 = 3248; + priv->factor.thcode_2 = 2800; + priv->factor.thcode_3 = 2221; + break; + case 1: + priv->factor.thcode_1 = 3245; + priv->factor.thcode_2 = 2795; + priv->factor.thcode_3 = 2216; + break; + case 2: + priv->factor.thcode_1 = 3250; + priv->factor.thcode_2 = 2805; + priv->factor.thcode_3 = 2237; + break; + } + + return 0; +} + +static void _linear_coefficient_calculation(struct rcar_gen3_thermal_priv *priv) +{ + int tj_2 = 0; + long a1, b1; + long a2, b2; + long a1_num, a1_den; + long a2_num, a2_den; + + tj_2 = (CODETSD((priv->factor.ptat_2 - priv->factor.ptat_3) * 137) + / (priv->factor.ptat_1 - priv->factor.ptat_3)) - CODETSD(41); + + /* + * The following code is to calculate coefficients for linear equation. + */ + /* Coefficient a1 and b1 */ + a1_num = CODETSD(priv->factor.thcode_2 - priv->factor.thcode_3); + a1_den = tj_2 - TJ_3; + a1 = (10000 * a1_num) / a1_den; + b1 = (10000 * priv->factor.thcode_3) - ((a1 * TJ_3) / 1000); + + /* Coefficient a2 and b2 */ + a2_num = CODETSD(priv->factor.thcode_2 - priv->factor.thcode_1); + a2_den = tj_2 - TJ_1; + a2 = (10000 * a2_num) / a2_den; + b2 = (10000 * priv->factor.thcode_1) - ((a2 * TJ_1) / 1000); + + priv->coef.a1 = DIV_ROUND_CLOSEST(a1, 10); + priv->coef.b1 = DIV_ROUND_CLOSEST(b1, 10); + priv->coef.a2 = DIV_ROUND_CLOSEST(a2, 10); + priv->coef.b2 = DIV_ROUND_CLOSEST(b2, 10); +} + +int _linear_temp_converter(struct equation_coefs coef, + int temp_code) +{ + int temp, temp1, temp2; + + temp1 = MCELSIUS((CODETSD(temp_code) - coef.b1)) / coef.a1; + temp2 = MCELSIUS((CODETSD(temp_code) - coef.b2)) / coef.a2; + temp = (temp1 + temp2) / 2; + + return _round_temp(temp); +} + +/* + * Zone device functions + */ +static int rcar_gen3_thermal_update_temp(struct rcar_gen3_thermal_priv *priv) +{ + u32 ctemp; + int i; + unsigned long flags; + u32 reg = REG_GEN3_IRQTEMP1 + (priv->id * 4); + + spin_lock_irqsave(&priv->lock, flags); + + for (i = 0; i < 256; i++) { + ctemp = thermal_reg_read(priv, REG_GEN3_TEMP) & CTEMP_MASK; + if (rcar_has_irq_support(priv)) { + thermal_reg_write(priv, reg, ctemp); + if (thermal_reg_read(priv, REG_GEN3_IRQSTR) != 0) + break; + } else + break; + + udelay(150); + } + + priv->ctemp = ctemp; + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int rcar_gen3_thermal_get_temp(void *devdata, int *temp) +{ + struct rcar_gen3_thermal_priv *priv = devdata; + int ctemp; + unsigned long flags; + + rcar_gen3_thermal_update_temp(priv); + + spin_lock_irqsave(&priv->lock, flags); + ctemp = _linear_temp_converter(priv->coef, priv->ctemp); + spin_unlock_irqrestore(&priv->lock, flags); + + if ((ctemp < MCELSIUS(-40)) || (ctemp > MCELSIUS(125))) { + struct device *dev = rcar_priv_to_dev(priv); + + dev_dbg(dev, "Temperature is not measured correctly!\n"); + return -EIO; + } + + *temp = ctemp; + + return 0; +} + +static int r8a7795_thermal_init(struct rcar_gen3_thermal_priv *priv) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + thermal_reg_write(priv, REG_GEN3_CTSR, THBGR); + thermal_reg_write(priv, REG_GEN3_CTSR, 0x0); + + udelay(1000); + + thermal_reg_write(priv, REG_GEN3_CTSR, PONM); + thermal_reg_write(priv, REG_GEN3_IRQCTL, 0x3F); + thermal_reg_write(priv, REG_GEN3_IRQEN, TEMP_IRQ_SHIFT(priv->id) | + TEMPD_IRQ_SHIFT(priv->id)); + thermal_reg_write(priv, REG_GEN3_CTSR, + PONM | AOUT | THBGR | VMEN); + udelay(100); + + thermal_reg_write(priv, REG_GEN3_CTSR, + PONM | AOUT | THBGR | VMEN | VMST | THSST); + + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +static int r8a7796_thermal_init(struct rcar_gen3_thermal_priv *priv) +{ + unsigned long flags; + unsigned long reg_val; + + spin_lock_irqsave(&priv->lock, flags); + thermal_reg_write(priv, REG_GEN3_THCTR, 0x0); + udelay(1000); + thermal_reg_write(priv, REG_GEN3_IRQCTL, 0x3F); + thermal_reg_write(priv, REG_GEN3_IRQEN, TEMP_IRQ_SHIFT(priv->id) | + TEMPD_IRQ_SHIFT(priv->id)); + thermal_reg_write(priv, REG_GEN3_THCTR, + CTCTL | THCNTSEN(BIT_LEN_12)); + reg_val = thermal_reg_read(priv, REG_GEN3_THCTR); + reg_val &= ~CTCTL; + reg_val |= THSST; + thermal_reg_write(priv, REG_GEN3_THCTR, reg_val); + + spin_unlock_irqrestore(&priv->lock, flags); + + return 0; +} + +/* + * Interrupt + */ +#define rcar_gen3_thermal_irq_enable(p) _thermal_irq_ctrl(p, 1) +#define rcar_gen3_thermal_irq_disable(p) _thermal_irq_ctrl(p, 0) +static void _thermal_irq_ctrl(struct rcar_gen3_thermal_priv *priv, int enable) +{ + unsigned long flags; + + if (!rcar_has_irq_support(priv)) + return; + + spin_lock_irqsave(&priv->lock, flags); + thermal_reg_write(priv, REG_GEN3_IRQMSK, + enable ? (TEMP_IRQ_SHIFT(priv->id) | + TEMPD_IRQ_SHIFT(priv->id)) : 0); + spin_unlock_irqrestore(&priv->lock, flags); +} + +static void rcar_gen3_thermal_work(struct work_struct *work) +{ + struct rcar_gen3_thermal_priv *priv; + + priv = container_of(work, struct rcar_gen3_thermal_priv, work.work); + + thermal_zone_device_update(priv->zone); + + rcar_gen3_thermal_irq_enable(priv); +} + +static irqreturn_t rcar_gen3_thermal_irq(int irq, void *data) +{ + struct rcar_gen3_thermal_priv *priv = data; + unsigned long flags; + int status; + + spin_lock_irqsave(&priv->lock, flags); + status = thermal_reg_read(priv, REG_GEN3_IRQSTR); + thermal_reg_write(priv, REG_GEN3_IRQSTR, 0); + spin_unlock_irqrestore(&priv->lock, flags); + + if ((status & TEMP_IRQ_SHIFT(priv->id)) || + (status & TEMPD_IRQ_SHIFT(priv->id))) { + rcar_gen3_thermal_irq_disable(priv); + schedule_delayed_work(&priv->work, + msecs_to_jiffies(300)); + } + + return IRQ_HANDLED; +} + +static struct thermal_zone_of_device_ops rcar_gen3_tz_of_ops = { + .get_temp = rcar_gen3_thermal_get_temp, +}; + +/* + * Platform functions + */ +static int rcar_gen3_thermal_remove(struct platform_device *pdev) +{ + struct rcar_gen3_thermal_priv *priv = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + rcar_gen3_thermal_irq_disable(priv); + thermal_zone_of_sensor_unregister(dev, priv->zone); + + pm_runtime_put(dev); + pm_runtime_disable(dev); + + return 0; +} + +static const struct rcar_gen3_thermal_data r8a7795_data = { + .thermal_init = r8a7795_thermal_init, +}; + +static const struct rcar_gen3_thermal_data r8a7796_data = { + .thermal_init = r8a7796_thermal_init, +}; + +static const struct of_device_id rcar_gen3_thermal_dt_ids[] = { + { .compatible = "renesas,thermal-r8a7795", .data = &r8a7795_data}, + { .compatible = "renesas,thermal-r8a7796", .data = &r8a7796_data}, + { .compatible = "renesas,rcar-gen3-thermal", .data = &r8a7796_data}, + {}, +}; +MODULE_DEVICE_TABLE(of, rcar_gen3_thermal_dt_ids); + +static int rcar_gen3_thermal_probe(struct platform_device *pdev) +{ + struct rcar_gen3_thermal_priv *priv; + struct device *dev = &pdev->dev; + struct resource *res, *irq; + int ret = -ENODEV; + int idle; + struct device_node *tz_nd, *tmp_nd; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->dev = dev; + + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + priv->data = of_device_get_match_data(dev); + if (!priv->data) + goto error_unregister; + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + priv->irq = 0; + if (irq) { + priv->irq = 1; + for_each_node_with_property(tz_nd, "polling-delay") { + tmp_nd = of_parse_phandle(tz_nd, + "thermal-sensors", 0); + if (tmp_nd && !strcmp(tmp_nd->full_name, + dev->of_node->full_name)) { + of_property_read_u32(tz_nd, "polling-delay", + &idle); + (idle > 0) ? (priv->irq = 0) : + (priv->irq = 1); + break; + } + } + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + goto error_unregister; + + priv->base = devm_ioremap_resource(dev, res); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); + goto error_unregister; + } + + spin_lock_init(&priv->lock); + INIT_DELAYED_WORK(&priv->work, rcar_gen3_thermal_work); + + priv->id = of_alias_get_id(dev->of_node, "tsc"); + + priv->zone = devm_thermal_zone_of_sensor_register(dev, 0, priv, + &rcar_gen3_tz_of_ops); + + if (IS_ERR(priv->zone)) { + dev_err(dev, "Can't register thermal zone\n"); + ret = PTR_ERR(priv->zone); + priv->zone = NULL; + goto error_unregister; + } + + priv->data->thermal_init(priv); + ret = _read_fuse_factor(priv); + if (ret) + goto error_unregister; + _linear_coefficient_calculation(priv); + ret = rcar_gen3_thermal_update_temp(priv); + + if (ret < 0) + goto error_unregister; + + + rcar_gen3_thermal_irq_enable(priv); + + /* Interrupt */ + if (irq) { + ret = devm_request_irq(dev, irq->start, + rcar_gen3_thermal_irq, 0, + dev_name(dev), priv); + if (ret) { + dev_err(dev, "IRQ request failed\n "); + goto error_unregister; + } + } + + dev_info(dev, "probed\n"); + + return 0; + +error_unregister: + rcar_gen3_thermal_remove(pdev); + + return ret; +} + +static struct platform_driver rcar_gen3_thermal_driver = { + .driver = { + .name = "rcar_gen3_thermal", + .of_match_table = rcar_gen3_thermal_dt_ids, + }, + .probe = rcar_gen3_thermal_probe, + .remove = rcar_gen3_thermal_remove, +}; +module_platform_driver(rcar_gen3_thermal_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("R-Car Gen3 THS/CIVM thermal sensor driver"); +MODULE_AUTHOR("Khiem Nguyen ");