diff options
Diffstat (limited to 'drivers/thermal/rcar_thermal.c')
-rw-r--r-- | drivers/thermal/rcar_thermal.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c new file mode 100644 index 00000000000..d4452716aaa --- /dev/null +++ b/drivers/thermal/rcar_thermal.c @@ -0,0 +1,260 @@ +/* + * R-Car THS/TSC thermal sensor driver + * + * Copyright (C) 2012 Renesas Solutions Corp. + * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com> + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/thermal.h> + +#define THSCR 0x2c +#define THSSR 0x30 + +/* THSCR */ +#define CPTAP 0xf + +/* THSSR */ +#define CTEMP 0x3f + + +struct rcar_thermal_priv { + void __iomem *base; + struct device *dev; + spinlock_t lock; + u32 comp; +}; + +/* + * basic functions + */ +static u32 rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) +{ + unsigned long flags; + u32 ret; + + spin_lock_irqsave(&priv->lock, flags); + + ret = ioread32(priv->base + reg); + + spin_unlock_irqrestore(&priv->lock, flags); + + return ret; +} + +#if 0 /* no user at this point */ +static void rcar_thermal_write(struct rcar_thermal_priv *priv, + u32 reg, u32 data) +{ + unsigned long flags; + + spin_lock_irqsave(&priv->lock, flags); + + iowrite32(data, priv->base + reg); + + spin_unlock_irqrestore(&priv->lock, flags); +} +#endif + +static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg, + u32 mask, u32 data) +{ + unsigned long flags; + u32 val; + + spin_lock_irqsave(&priv->lock, flags); + + val = ioread32(priv->base + reg); + val &= ~mask; + val |= (data & mask); + iowrite32(val, priv->base + reg); + + spin_unlock_irqrestore(&priv->lock, flags); +} + +/* + * zone device functions + */ +static int rcar_thermal_get_temp(struct thermal_zone_device *zone, + unsigned long *temp) +{ + struct rcar_thermal_priv *priv = zone->devdata; + int val, min, max, tmp; + + tmp = -200; /* default */ + while (1) { + if (priv->comp < 1 || priv->comp > 12) { + dev_err(priv->dev, + "THSSR invalid data (%d)\n", priv->comp); + priv->comp = 4; /* for next thermal */ + return -EINVAL; + } + + /* + * THS comparator offset and the reference temperature + * + * Comparator | reference | Temperature field + * offset | temperature | measurement + * | (degrees C) | (degrees C) + * -------------+---------------+------------------- + * 1 | -45 | -45 to -30 + * 2 | -30 | -30 to -15 + * 3 | -15 | -15 to 0 + * 4 | 0 | 0 to +15 + * 5 | +15 | +15 to +30 + * 6 | +30 | +30 to +45 + * 7 | +45 | +45 to +60 + * 8 | +60 | +60 to +75 + * 9 | +75 | +75 to +90 + * 10 | +90 | +90 to +105 + * 11 | +105 | +105 to +120 + * 12 | +120 | +120 to +135 + */ + + /* calculate thermal limitation */ + min = (priv->comp * 15) - 60; + max = min + 15; + + /* + * we need to wait 300us after changing comparator offset + * to get stable temperature. + * see "Usage Notes" on datasheet + */ + rcar_thermal_bset(priv, THSCR, CPTAP, priv->comp); + udelay(300); + + /* calculate current temperature */ + val = rcar_thermal_read(priv, THSSR) & CTEMP; + val = (val * 5) - 65; + + dev_dbg(priv->dev, "comp/min/max/val = %d/%d/%d/%d\n", + priv->comp, min, max, val); + + /* + * If val is same as min/max, then, + * it should try again on next comparator. + * But the val might be correct temperature. + * Keep it on "tmp" and compare with next val. + */ + if (tmp == val) + break; + + if (val <= min) { + tmp = min; + priv->comp--; /* try again */ + } else if (val >= max) { + tmp = max; + priv->comp++; /* try again */ + } else { + tmp = val; + break; + } + } + + *temp = tmp; + return 0; +} + +static struct thermal_zone_device_ops rcar_thermal_zone_ops = { + .get_temp = rcar_thermal_get_temp, +}; + +/* + * platform functions + */ +static int rcar_thermal_probe(struct platform_device *pdev) +{ + struct thermal_zone_device *zone; + struct rcar_thermal_priv *priv; + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Could not get platform resource\n"); + return -ENODEV; + } + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "Could not allocate priv\n"); + return -ENOMEM; + } + + priv->comp = 4; /* basic setup */ + priv->dev = &pdev->dev; + spin_lock_init(&priv->lock); + priv->base = devm_ioremap_nocache(&pdev->dev, + res->start, resource_size(res)); + if (!priv->base) { + dev_err(&pdev->dev, "Unable to ioremap thermal register\n"); + ret = -ENOMEM; + goto error_free_priv; + } + + zone = thermal_zone_device_register("rcar_thermal", 0, priv, + &rcar_thermal_zone_ops, 0, 0); + if (IS_ERR(zone)) { + dev_err(&pdev->dev, "thermal zone device is NULL\n"); + ret = PTR_ERR(zone); + goto error_iounmap; + } + + platform_set_drvdata(pdev, zone); + + dev_info(&pdev->dev, "proved\n"); + + return 0; + +error_iounmap: + devm_iounmap(&pdev->dev, priv->base); +error_free_priv: + devm_kfree(&pdev->dev, priv); + + return ret; +} + +static int rcar_thermal_remove(struct platform_device *pdev) +{ + struct thermal_zone_device *zone = platform_get_drvdata(pdev); + struct rcar_thermal_priv *priv = zone->devdata; + + thermal_zone_device_unregister(zone); + platform_set_drvdata(pdev, NULL); + + devm_iounmap(&pdev->dev, priv->base); + devm_kfree(&pdev->dev, priv); + + return 0; +} + +static struct platform_driver rcar_thermal_driver = { + .driver = { + .name = "rcar_thermal", + }, + .probe = rcar_thermal_probe, + .remove = rcar_thermal_remove, +}; +module_platform_driver(rcar_thermal_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver"); +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>"); |