11e426ffdSKuninori Morimoto /*
21e426ffdSKuninori Morimoto  *  R-Car THS/TSC thermal sensor driver
31e426ffdSKuninori Morimoto  *
41e426ffdSKuninori Morimoto  * Copyright (C) 2012 Renesas Solutions Corp.
51e426ffdSKuninori Morimoto  * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
61e426ffdSKuninori Morimoto  *
71e426ffdSKuninori Morimoto  *  This program is free software; you can redistribute it and/or modify
81e426ffdSKuninori Morimoto  *  it under the terms of the GNU General Public License as published by
91e426ffdSKuninori Morimoto  *  the Free Software Foundation; version 2 of the License.
101e426ffdSKuninori Morimoto  *
111e426ffdSKuninori Morimoto  *  This program is distributed in the hope that it will be useful, but
121e426ffdSKuninori Morimoto  *  WITHOUT ANY WARRANTY; without even the implied warranty of
131e426ffdSKuninori Morimoto  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
141e426ffdSKuninori Morimoto  *  General Public License for more details.
151e426ffdSKuninori Morimoto  *
161e426ffdSKuninori Morimoto  *  You should have received a copy of the GNU General Public License along
171e426ffdSKuninori Morimoto  *  with this program; if not, write to the Free Software Foundation, Inc.,
181e426ffdSKuninori Morimoto  *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
191e426ffdSKuninori Morimoto  */
201e426ffdSKuninori Morimoto #include <linux/delay.h>
211e426ffdSKuninori Morimoto #include <linux/err.h>
221e426ffdSKuninori Morimoto #include <linux/io.h>
231e426ffdSKuninori Morimoto #include <linux/module.h>
241e426ffdSKuninori Morimoto #include <linux/platform_device.h>
25d2a73e22Skuninori.morimoto.gx@renesas.com #include <linux/reboot.h>
261e426ffdSKuninori Morimoto #include <linux/slab.h>
271e426ffdSKuninori Morimoto #include <linux/spinlock.h>
281e426ffdSKuninori Morimoto #include <linux/thermal.h>
291e426ffdSKuninori Morimoto 
30d2a73e22Skuninori.morimoto.gx@renesas.com #define IDLE_INTERVAL	5000
31d2a73e22Skuninori.morimoto.gx@renesas.com 
321e426ffdSKuninori Morimoto #define THSCR	0x2c
331e426ffdSKuninori Morimoto #define THSSR	0x30
341e426ffdSKuninori Morimoto 
351e426ffdSKuninori Morimoto /* THSCR */
361e426ffdSKuninori Morimoto #define CPTAP	0xf
371e426ffdSKuninori Morimoto 
381e426ffdSKuninori Morimoto /* THSSR */
391e426ffdSKuninori Morimoto #define CTEMP	0x3f
401e426ffdSKuninori Morimoto 
411e426ffdSKuninori Morimoto 
421e426ffdSKuninori Morimoto struct rcar_thermal_priv {
431e426ffdSKuninori Morimoto 	void __iomem *base;
441e426ffdSKuninori Morimoto 	struct device *dev;
451e426ffdSKuninori Morimoto 	spinlock_t lock;
461e426ffdSKuninori Morimoto 	u32 comp;
471e426ffdSKuninori Morimoto };
481e426ffdSKuninori Morimoto 
49c499703eSKuninori Morimoto #define MCELSIUS(temp)			((temp) * 1000)
509dde8f86SKuninori Morimoto #define rcar_zone_to_priv(zone)		((zone)->devdata)
51c499703eSKuninori Morimoto 
521e426ffdSKuninori Morimoto /*
531e426ffdSKuninori Morimoto  *		basic functions
541e426ffdSKuninori Morimoto  */
551e426ffdSKuninori Morimoto static u32 rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg)
561e426ffdSKuninori Morimoto {
571e426ffdSKuninori Morimoto 	unsigned long flags;
581e426ffdSKuninori Morimoto 	u32 ret;
591e426ffdSKuninori Morimoto 
601e426ffdSKuninori Morimoto 	spin_lock_irqsave(&priv->lock, flags);
611e426ffdSKuninori Morimoto 
621e426ffdSKuninori Morimoto 	ret = ioread32(priv->base + reg);
631e426ffdSKuninori Morimoto 
641e426ffdSKuninori Morimoto 	spin_unlock_irqrestore(&priv->lock, flags);
651e426ffdSKuninori Morimoto 
661e426ffdSKuninori Morimoto 	return ret;
671e426ffdSKuninori Morimoto }
681e426ffdSKuninori Morimoto 
691e426ffdSKuninori Morimoto #if 0 /* no user at this point */
701e426ffdSKuninori Morimoto static void rcar_thermal_write(struct rcar_thermal_priv *priv,
711e426ffdSKuninori Morimoto 			       u32 reg, u32 data)
721e426ffdSKuninori Morimoto {
731e426ffdSKuninori Morimoto 	unsigned long flags;
741e426ffdSKuninori Morimoto 
751e426ffdSKuninori Morimoto 	spin_lock_irqsave(&priv->lock, flags);
761e426ffdSKuninori Morimoto 
771e426ffdSKuninori Morimoto 	iowrite32(data, priv->base + reg);
781e426ffdSKuninori Morimoto 
791e426ffdSKuninori Morimoto 	spin_unlock_irqrestore(&priv->lock, flags);
801e426ffdSKuninori Morimoto }
811e426ffdSKuninori Morimoto #endif
821e426ffdSKuninori Morimoto 
831e426ffdSKuninori Morimoto static void rcar_thermal_bset(struct rcar_thermal_priv *priv, u32 reg,
841e426ffdSKuninori Morimoto 			      u32 mask, u32 data)
851e426ffdSKuninori Morimoto {
861e426ffdSKuninori Morimoto 	unsigned long flags;
871e426ffdSKuninori Morimoto 	u32 val;
881e426ffdSKuninori Morimoto 
891e426ffdSKuninori Morimoto 	spin_lock_irqsave(&priv->lock, flags);
901e426ffdSKuninori Morimoto 
911e426ffdSKuninori Morimoto 	val = ioread32(priv->base + reg);
921e426ffdSKuninori Morimoto 	val &= ~mask;
931e426ffdSKuninori Morimoto 	val |= (data & mask);
941e426ffdSKuninori Morimoto 	iowrite32(val, priv->base + reg);
951e426ffdSKuninori Morimoto 
961e426ffdSKuninori Morimoto 	spin_unlock_irqrestore(&priv->lock, flags);
971e426ffdSKuninori Morimoto }
981e426ffdSKuninori Morimoto 
991e426ffdSKuninori Morimoto /*
1001e426ffdSKuninori Morimoto  *		zone device functions
1011e426ffdSKuninori Morimoto  */
1021e426ffdSKuninori Morimoto static int rcar_thermal_get_temp(struct thermal_zone_device *zone,
1031e426ffdSKuninori Morimoto 			   unsigned long *temp)
1041e426ffdSKuninori Morimoto {
105d12250efSKuninori Morimoto 	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
1061e426ffdSKuninori Morimoto 	int val, min, max, tmp;
1071e426ffdSKuninori Morimoto 
1081e426ffdSKuninori Morimoto 	tmp = -200; /* default */
1091e426ffdSKuninori Morimoto 	while (1) {
1101e426ffdSKuninori Morimoto 		if (priv->comp < 1 || priv->comp > 12) {
1111e426ffdSKuninori Morimoto 			dev_err(priv->dev,
1121e426ffdSKuninori Morimoto 				"THSSR invalid data (%d)\n", priv->comp);
1131e426ffdSKuninori Morimoto 			priv->comp = 4; /* for next thermal */
1141e426ffdSKuninori Morimoto 			return -EINVAL;
1151e426ffdSKuninori Morimoto 		}
1161e426ffdSKuninori Morimoto 
1171e426ffdSKuninori Morimoto 		/*
1181e426ffdSKuninori Morimoto 		 * THS comparator offset and the reference temperature
1191e426ffdSKuninori Morimoto 		 *
1201e426ffdSKuninori Morimoto 		 * Comparator	| reference	| Temperature field
1211e426ffdSKuninori Morimoto 		 * offset	| temperature	| measurement
1221e426ffdSKuninori Morimoto 		 *		| (degrees C)	| (degrees C)
1231e426ffdSKuninori Morimoto 		 * -------------+---------------+-------------------
1241e426ffdSKuninori Morimoto 		 *  1		|  -45		|  -45 to  -30
1251e426ffdSKuninori Morimoto 		 *  2		|  -30		|  -30 to  -15
1261e426ffdSKuninori Morimoto 		 *  3		|  -15		|  -15 to    0
1271e426ffdSKuninori Morimoto 		 *  4		|    0		|    0 to  +15
1281e426ffdSKuninori Morimoto 		 *  5		|  +15		|  +15 to  +30
1291e426ffdSKuninori Morimoto 		 *  6		|  +30		|  +30 to  +45
1301e426ffdSKuninori Morimoto 		 *  7		|  +45		|  +45 to  +60
1311e426ffdSKuninori Morimoto 		 *  8		|  +60		|  +60 to  +75
1321e426ffdSKuninori Morimoto 		 *  9		|  +75		|  +75 to  +90
1331e426ffdSKuninori Morimoto 		 * 10		|  +90		|  +90 to +105
1341e426ffdSKuninori Morimoto 		 * 11		| +105		| +105 to +120
1351e426ffdSKuninori Morimoto 		 * 12		| +120		| +120 to +135
1361e426ffdSKuninori Morimoto 		 */
1371e426ffdSKuninori Morimoto 
1381e426ffdSKuninori Morimoto 		/* calculate thermal limitation */
1391e426ffdSKuninori Morimoto 		min = (priv->comp * 15) - 60;
1401e426ffdSKuninori Morimoto 		max = min + 15;
1411e426ffdSKuninori Morimoto 
1421e426ffdSKuninori Morimoto 		/*
1431e426ffdSKuninori Morimoto 		 * we need to wait 300us after changing comparator offset
1441e426ffdSKuninori Morimoto 		 * to get stable temperature.
1451e426ffdSKuninori Morimoto 		 * see "Usage Notes" on datasheet
1461e426ffdSKuninori Morimoto 		 */
1471e426ffdSKuninori Morimoto 		rcar_thermal_bset(priv, THSCR, CPTAP, priv->comp);
1481e426ffdSKuninori Morimoto 		udelay(300);
1491e426ffdSKuninori Morimoto 
1501e426ffdSKuninori Morimoto 		/* calculate current temperature */
1511e426ffdSKuninori Morimoto 		val = rcar_thermal_read(priv, THSSR) & CTEMP;
1521e426ffdSKuninori Morimoto 		val = (val * 5) - 65;
1531e426ffdSKuninori Morimoto 
1541e426ffdSKuninori Morimoto 		dev_dbg(priv->dev, "comp/min/max/val = %d/%d/%d/%d\n",
1551e426ffdSKuninori Morimoto 			priv->comp, min, max, val);
1561e426ffdSKuninori Morimoto 
1571e426ffdSKuninori Morimoto 		/*
1581e426ffdSKuninori Morimoto 		 * If val is same as min/max, then,
1591e426ffdSKuninori Morimoto 		 * it should try again on next comparator.
1601e426ffdSKuninori Morimoto 		 * But the val might be correct temperature.
1611e426ffdSKuninori Morimoto 		 * Keep it on "tmp" and compare with next val.
1621e426ffdSKuninori Morimoto 		 */
1631e426ffdSKuninori Morimoto 		if (tmp == val)
1641e426ffdSKuninori Morimoto 			break;
1651e426ffdSKuninori Morimoto 
1661e426ffdSKuninori Morimoto 		if (val <= min) {
1671e426ffdSKuninori Morimoto 			tmp = min;
1681e426ffdSKuninori Morimoto 			priv->comp--; /* try again */
1691e426ffdSKuninori Morimoto 		} else if (val >= max) {
1701e426ffdSKuninori Morimoto 			tmp = max;
1711e426ffdSKuninori Morimoto 			priv->comp++; /* try again */
1721e426ffdSKuninori Morimoto 		} else {
1731e426ffdSKuninori Morimoto 			tmp = val;
1741e426ffdSKuninori Morimoto 			break;
1751e426ffdSKuninori Morimoto 		}
1761e426ffdSKuninori Morimoto 	}
1771e426ffdSKuninori Morimoto 
178c499703eSKuninori Morimoto 	*temp = MCELSIUS(tmp);
1791e426ffdSKuninori Morimoto 	return 0;
1801e426ffdSKuninori Morimoto }
1811e426ffdSKuninori Morimoto 
182d2a73e22Skuninori.morimoto.gx@renesas.com static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone,
183d2a73e22Skuninori.morimoto.gx@renesas.com 				      int trip, enum thermal_trip_type *type)
184d2a73e22Skuninori.morimoto.gx@renesas.com {
185d2a73e22Skuninori.morimoto.gx@renesas.com 	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
186d2a73e22Skuninori.morimoto.gx@renesas.com 
187d2a73e22Skuninori.morimoto.gx@renesas.com 	/* see rcar_thermal_get_temp() */
188d2a73e22Skuninori.morimoto.gx@renesas.com 	switch (trip) {
189d2a73e22Skuninori.morimoto.gx@renesas.com 	case 0: /* +90 <= temp */
190d2a73e22Skuninori.morimoto.gx@renesas.com 		*type = THERMAL_TRIP_CRITICAL;
191d2a73e22Skuninori.morimoto.gx@renesas.com 		break;
192d2a73e22Skuninori.morimoto.gx@renesas.com 	default:
193d2a73e22Skuninori.morimoto.gx@renesas.com 		dev_err(priv->dev, "rcar driver trip error\n");
194d2a73e22Skuninori.morimoto.gx@renesas.com 		return -EINVAL;
195d2a73e22Skuninori.morimoto.gx@renesas.com 	}
196d2a73e22Skuninori.morimoto.gx@renesas.com 
197d2a73e22Skuninori.morimoto.gx@renesas.com 	return 0;
198d2a73e22Skuninori.morimoto.gx@renesas.com }
199d2a73e22Skuninori.morimoto.gx@renesas.com 
200d2a73e22Skuninori.morimoto.gx@renesas.com static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone,
201d2a73e22Skuninori.morimoto.gx@renesas.com 				      int trip, unsigned long *temp)
202d2a73e22Skuninori.morimoto.gx@renesas.com {
203d2a73e22Skuninori.morimoto.gx@renesas.com 	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
204d2a73e22Skuninori.morimoto.gx@renesas.com 
205d2a73e22Skuninori.morimoto.gx@renesas.com 	/* see rcar_thermal_get_temp() */
206d2a73e22Skuninori.morimoto.gx@renesas.com 	switch (trip) {
207d2a73e22Skuninori.morimoto.gx@renesas.com 	case 0: /* +90 <= temp */
208d2a73e22Skuninori.morimoto.gx@renesas.com 		*temp = MCELSIUS(90);
209d2a73e22Skuninori.morimoto.gx@renesas.com 		break;
210d2a73e22Skuninori.morimoto.gx@renesas.com 	default:
211d2a73e22Skuninori.morimoto.gx@renesas.com 		dev_err(priv->dev, "rcar driver trip error\n");
212d2a73e22Skuninori.morimoto.gx@renesas.com 		return -EINVAL;
213d2a73e22Skuninori.morimoto.gx@renesas.com 	}
214d2a73e22Skuninori.morimoto.gx@renesas.com 
215d2a73e22Skuninori.morimoto.gx@renesas.com 	return 0;
216d2a73e22Skuninori.morimoto.gx@renesas.com }
217d2a73e22Skuninori.morimoto.gx@renesas.com 
218d2a73e22Skuninori.morimoto.gx@renesas.com static int rcar_thermal_notify(struct thermal_zone_device *zone,
219d2a73e22Skuninori.morimoto.gx@renesas.com 			       int trip, enum thermal_trip_type type)
220d2a73e22Skuninori.morimoto.gx@renesas.com {
221d2a73e22Skuninori.morimoto.gx@renesas.com 	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone);
222d2a73e22Skuninori.morimoto.gx@renesas.com 
223d2a73e22Skuninori.morimoto.gx@renesas.com 	switch (type) {
224d2a73e22Skuninori.morimoto.gx@renesas.com 	case THERMAL_TRIP_CRITICAL:
225d2a73e22Skuninori.morimoto.gx@renesas.com 		/* FIXME */
226d2a73e22Skuninori.morimoto.gx@renesas.com 		dev_warn(priv->dev,
227d2a73e22Skuninori.morimoto.gx@renesas.com 			 "Thermal reached to critical temperature\n");
228d2a73e22Skuninori.morimoto.gx@renesas.com 		machine_power_off();
229d2a73e22Skuninori.morimoto.gx@renesas.com 		break;
230d2a73e22Skuninori.morimoto.gx@renesas.com 	default:
231d2a73e22Skuninori.morimoto.gx@renesas.com 		break;
232d2a73e22Skuninori.morimoto.gx@renesas.com 	}
233d2a73e22Skuninori.morimoto.gx@renesas.com 
234d2a73e22Skuninori.morimoto.gx@renesas.com 	return 0;
235d2a73e22Skuninori.morimoto.gx@renesas.com }
236d2a73e22Skuninori.morimoto.gx@renesas.com 
2371e426ffdSKuninori Morimoto static struct thermal_zone_device_ops rcar_thermal_zone_ops = {
2381e426ffdSKuninori Morimoto 	.get_temp	= rcar_thermal_get_temp,
239d2a73e22Skuninori.morimoto.gx@renesas.com 	.get_trip_type	= rcar_thermal_get_trip_type,
240d2a73e22Skuninori.morimoto.gx@renesas.com 	.get_trip_temp	= rcar_thermal_get_trip_temp,
241d2a73e22Skuninori.morimoto.gx@renesas.com 	.notify		= rcar_thermal_notify,
2421e426ffdSKuninori Morimoto };
2431e426ffdSKuninori Morimoto 
2441e426ffdSKuninori Morimoto /*
2451e426ffdSKuninori Morimoto  *		platform functions
2461e426ffdSKuninori Morimoto  */
2471e426ffdSKuninori Morimoto static int rcar_thermal_probe(struct platform_device *pdev)
2481e426ffdSKuninori Morimoto {
2491e426ffdSKuninori Morimoto 	struct thermal_zone_device *zone;
2501e426ffdSKuninori Morimoto 	struct rcar_thermal_priv *priv;
2511e426ffdSKuninori Morimoto 	struct resource *res;
2521e426ffdSKuninori Morimoto 
2531e426ffdSKuninori Morimoto 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2541e426ffdSKuninori Morimoto 	if (!res) {
2551e426ffdSKuninori Morimoto 		dev_err(&pdev->dev, "Could not get platform resource\n");
2561e426ffdSKuninori Morimoto 		return -ENODEV;
2571e426ffdSKuninori Morimoto 	}
2581e426ffdSKuninori Morimoto 
2591e426ffdSKuninori Morimoto 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
2601e426ffdSKuninori Morimoto 	if (!priv) {
2611e426ffdSKuninori Morimoto 		dev_err(&pdev->dev, "Could not allocate priv\n");
2621e426ffdSKuninori Morimoto 		return -ENOMEM;
2631e426ffdSKuninori Morimoto 	}
2641e426ffdSKuninori Morimoto 
2651e426ffdSKuninori Morimoto 	priv->comp = 4; /* basic setup */
2661e426ffdSKuninori Morimoto 	priv->dev = &pdev->dev;
2671e426ffdSKuninori Morimoto 	spin_lock_init(&priv->lock);
2681e426ffdSKuninori Morimoto 	priv->base = devm_ioremap_nocache(&pdev->dev,
2691e426ffdSKuninori Morimoto 					  res->start, resource_size(res));
2701e426ffdSKuninori Morimoto 	if (!priv->base) {
2711e426ffdSKuninori Morimoto 		dev_err(&pdev->dev, "Unable to ioremap thermal register\n");
2724e8e2f64SKuninori Morimoto 		return -ENOMEM;
2731e426ffdSKuninori Morimoto 	}
2741e426ffdSKuninori Morimoto 
275d2a73e22Skuninori.morimoto.gx@renesas.com 	zone = thermal_zone_device_register("rcar_thermal", 1, 0, priv,
276d2a73e22Skuninori.morimoto.gx@renesas.com 					    &rcar_thermal_zone_ops, NULL, 0,
277d2a73e22Skuninori.morimoto.gx@renesas.com 					    IDLE_INTERVAL);
2781e426ffdSKuninori Morimoto 	if (IS_ERR(zone)) {
2791e426ffdSKuninori Morimoto 		dev_err(&pdev->dev, "thermal zone device is NULL\n");
2804e8e2f64SKuninori Morimoto 		return PTR_ERR(zone);
2811e426ffdSKuninori Morimoto 	}
2821e426ffdSKuninori Morimoto 
2831e426ffdSKuninori Morimoto 	platform_set_drvdata(pdev, zone);
2841e426ffdSKuninori Morimoto 
2851e426ffdSKuninori Morimoto 	dev_info(&pdev->dev, "proved\n");
2861e426ffdSKuninori Morimoto 
2871e426ffdSKuninori Morimoto 	return 0;
2881e426ffdSKuninori Morimoto }
2891e426ffdSKuninori Morimoto 
2901e426ffdSKuninori Morimoto static int rcar_thermal_remove(struct platform_device *pdev)
2911e426ffdSKuninori Morimoto {
2921e426ffdSKuninori Morimoto 	struct thermal_zone_device *zone = platform_get_drvdata(pdev);
2931e426ffdSKuninori Morimoto 
2941e426ffdSKuninori Morimoto 	thermal_zone_device_unregister(zone);
2951e426ffdSKuninori Morimoto 	platform_set_drvdata(pdev, NULL);
2961e426ffdSKuninori Morimoto 
2971e426ffdSKuninori Morimoto 	return 0;
2981e426ffdSKuninori Morimoto }
2991e426ffdSKuninori Morimoto 
3001e426ffdSKuninori Morimoto static struct platform_driver rcar_thermal_driver = {
3011e426ffdSKuninori Morimoto 	.driver	= {
3021e426ffdSKuninori Morimoto 		.name	= "rcar_thermal",
3031e426ffdSKuninori Morimoto 	},
3041e426ffdSKuninori Morimoto 	.probe		= rcar_thermal_probe,
3051e426ffdSKuninori Morimoto 	.remove		= rcar_thermal_remove,
3061e426ffdSKuninori Morimoto };
3071e426ffdSKuninori Morimoto module_platform_driver(rcar_thermal_driver);
3081e426ffdSKuninori Morimoto 
3091e426ffdSKuninori Morimoto MODULE_LICENSE("GPL");
3101e426ffdSKuninori Morimoto MODULE_DESCRIPTION("R-Car THS/TSC thermal sensor driver");
3111e426ffdSKuninori Morimoto MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
312