From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753253AbcICF1E (ORCPT ); Sat, 3 Sep 2016 01:27:04 -0400 Received: from relmlor1.renesas.com ([210.160.252.171]:44723 "EHLO relmlie4.idc.renesas.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1751586AbcICF04 (ORCPT ); Sat, 3 Sep 2016 01:26:56 -0400 X-IronPort-AV: E=Sophos;i="5.22,559,1449500400"; d="scan'208";a="218798139" From: Khiem Nguyen To: Kuninori Morimoto 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 Dang , Toru Oishi , "Khiem Nguyen" Subject: [PATCH 2/5 v2] thermal: rcar_gen3_thermal: Add R-Car Gen3 thermal driver Thread-Topic: [PATCH 2/5 v2] thermal: rcar_gen3_thermal: Add R-Car Gen3 thermal driver Thread-Index: AdIFo4POMibepP/+Tzq5ArzmzhbzYA== Date: Sat, 3 Sep 2016 05:25:56 +0000 Message-ID: Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: authentication-results: spf=none (sender IP is ) smtp.mailfrom=khiem.nguyen.xt@renesas.com; x-originating-ip: [218.213.70.132] x-ms-office365-filtering-correlation-id: 5205bb4c-34da-4b66-e0fd-08d3d3bac831 x-microsoft-exchange-diagnostics: 1;SIXPR06MB0906;20:jhSpdmtlxZOyH5IF8LRZJFLkm/JGW9Fg7J9MMWjDDMlVoopP35TshESCl9Q9gRXWIQ7Ya7IwfPnulDC0yxuSaOtWsp4AwbJ4Dbmpm7ivv5Ir8sZBAe5vRHDK8kcqoQAlNeQz724eZ0UGnwEqVHHyG3ggYvA1iSEBc0HsPV8EXGo= x-microsoft-antispam: UriScan:;BCL:0;PCL:0;RULEID:;SRVR:SIXPR06MB0906; x-microsoft-antispam-prvs: x-exchange-antispam-report-test: UriScan:(277106579953875); x-exchange-antispam-report-cfa-test: BCL:0;PCL:0;RULEID:(6040176)(601004)(2401047)(8121501046)(5005006)(3002001)(10201501046)(6055026);SRVR:SIXPR06MB0906;BCL:0;PCL:0;RULEID:;SRVR:SIXPR06MB0906; x-forefront-prvs: 00540983E2 x-forefront-antispam-report: SFV:NSPM;SFS:(10019020)(6009001)(7916002)(189002)(199003)(4001430100002)(586003)(8676002)(6116002)(7696003)(2900100001)(305945005)(102836003)(81166006)(74316002)(81156014)(7846002)(229853001)(9686002)(7416002)(33656002)(3846002)(7736002)(8936002)(2906002)(66066001)(4326007)(10400500002)(106356001)(68736007)(19580395003)(3660700001)(77096005)(107886002)(4001450100002)(11100500001)(122556002)(5660300001)(50986999)(101416001)(105586002)(5002640100001)(189998001)(575784001)(19580405001)(3280700002)(97736004)(54356999)(110136002)(76576001)(87936001)(86362001)(92566002)(15760500001)(2004002);DIR:OUT;SFP:1102;SCL:1;SRVR:SIXPR06MB0906;H:SIXPR06MB0906.apcprd06.prod.outlook.com;FPR:;SPF:None;PTR:InfoNoRecords;MX:1;A:1;LANG:en; spamdiagnosticoutput: 1:99 spamdiagnosticmetadata: NSPM Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 X-OriginatorOrg: renesas.com X-MS-Exchange-CrossTenant-originalarrivaltime: 03 Sep 2016 05:25:56.3409 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 53d82571-da19-47e4-9cb4-625a166a4a2a X-MS-Exchange-Transport-CrossTenantHeadersStamped: SIXPR06MB0906 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: 8bit X-MIME-Autoconverted: from base64 to 8bit by mail.home.local id u835R89A023329 Signed-off-by: Hien Dang Signed-off-by: Thao Nguyen Signed-off-by: Khiem Nguyen --- v2: * Set static function for _linear_temp_converter(). * Update the compatible string following new format. * Add newline to improve readability. * Change thermal_init callbacks to void functions. * Improve the processing to register thermal_zones. drivers/thermal/Kconfig | 9 + drivers/thermal/Makefile | 1 + drivers/thermal/rcar_gen3_thermal.c | 539 ++++++++++++++++++++++++++++++++++++ 3 files changed, 549 insertions(+) create mode 100644 drivers/thermal/rcar_gen3_thermal.c diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 900d505..8500a0a 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -233,6 +233,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 d091134..b7e7082 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..cdaaa75 --- /dev/null +++ b/drivers/thermal/rcar_gen3_thermal.c @@ -0,0 +1,539 @@ +/* + * R-Car Gen3 THS 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 { + void (*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); +} + +static 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 void 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); +} + +static void 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); +} + +/* + * 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,r8a7795-thermal", .data = &r8a7795_data}, + { .compatible = "renesas,r8a7796-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; + struct thermal_zone_device *zone; + + 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; + + priv->irq = 0; + + irq = platform_get_resource(pdev, IORESOURCE_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); + if (idle > 0) + priv->irq = 0; + 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"); + if (priv->id < 0) { + dev_err(dev, "Failed to get alias id (%d)\n", priv->id); + ret = priv->id; + goto error_unregister; + } + + zone = devm_thermal_zone_of_sensor_register(dev, 0, priv, + &rcar_gen3_tz_of_ops); + + if (IS_ERR(zone)) { + dev_err(dev, "Can't register thermal zone\n"); + ret = PTR_ERR(zone); + goto error_unregister; + } + priv->zone = zone; + + 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 thermal sensor driver"); +MODULE_AUTHOR("Khiem Nguyen "); -- 1.9.1