diff options
Diffstat (limited to 'drivers/thermal/rcar_thermal.c')
| -rw-r--r-- | drivers/thermal/rcar_thermal.c | 490 | 
1 files changed, 369 insertions, 121 deletions
diff --git a/drivers/thermal/rcar_thermal.c b/drivers/thermal/rcar_thermal.c index 90db951725d..28f09199401 100644 --- a/drivers/thermal/rcar_thermal.c +++ b/drivers/thermal/rcar_thermal.c @@ -19,225 +19,473 @@   */  #include <linux/delay.h>  #include <linux/err.h> +#include <linux/irq.h> +#include <linux/interrupt.h>  #include <linux/io.h>  #include <linux/module.h>  #include <linux/platform_device.h> +#include <linux/reboot.h>  #include <linux/slab.h>  #include <linux/spinlock.h>  #include <linux/thermal.h> -#define THSCR	0x2c -#define THSSR	0x30 +#define IDLE_INTERVAL	5000 + +#define COMMON_STR	0x00 +#define COMMON_ENR	0x04 +#define COMMON_INTMSK	0x0c + +#define REG_POSNEG	0x20 +#define REG_FILONOFF	0x28 +#define REG_THSCR	0x2c +#define REG_THSSR	0x30 +#define REG_INTCTRL	0x34  /* THSCR */ -#define CPTAP	0xf +#define CPCTL	(1 << 12)  /* THSSR */  #define CTEMP	0x3f - -struct rcar_thermal_priv { +struct rcar_thermal_common {  	void __iomem *base;  	struct device *dev; +	struct list_head head;  	spinlock_t lock; -	u32 comp;  }; +struct rcar_thermal_priv { +	void __iomem *base; +	struct rcar_thermal_common *common; +	struct thermal_zone_device *zone; +	struct delayed_work work; +	struct mutex lock; +	struct list_head list; +	int id; +	int ctemp; +}; + +#define rcar_thermal_for_each_priv(pos, common)	\ +	list_for_each_entry(pos, &common->head, list) +  #define MCELSIUS(temp)			((temp) * 1000) -#define rcar_zone_to_priv(zone)		(zone->devdata) +#define rcar_zone_to_priv(zone)		((zone)->devdata) +#define rcar_priv_to_dev(priv)		((priv)->common->dev) +#define rcar_has_irq_support(priv)	((priv)->common->base) +#define rcar_id_to_shift(priv)		((priv)->id * 8) + +#ifdef DEBUG +# define rcar_force_update_temp(priv)	1 +#else +# define rcar_force_update_temp(priv)	0 +#endif  /*   *		basic functions   */ -static u32 rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg) +#define rcar_thermal_common_read(c, r) \ +	_rcar_thermal_common_read(c, COMMON_ ##r) +static u32 _rcar_thermal_common_read(struct rcar_thermal_common *common, +				     u32 reg)  { -	unsigned long flags; -	u32 ret; - -	spin_lock_irqsave(&priv->lock, flags); +	return ioread32(common->base + reg); +} -	ret = ioread32(priv->base + reg); +#define rcar_thermal_common_write(c, r, d) \ +	_rcar_thermal_common_write(c, COMMON_ ##r, d) +static void _rcar_thermal_common_write(struct rcar_thermal_common *common, +				       u32 reg, u32 data) +{ +	iowrite32(data, common->base + reg); +} -	spin_unlock_irqrestore(&priv->lock, flags); +#define rcar_thermal_common_bset(c, r, m, d) \ +	_rcar_thermal_common_bset(c, COMMON_ ##r, m, d) +static void _rcar_thermal_common_bset(struct rcar_thermal_common *common, +				      u32 reg, u32 mask, u32 data) +{ +	u32 val; -	return ret; +	val = ioread32(common->base + reg); +	val &= ~mask; +	val |= (data & mask); +	iowrite32(val, common->base + reg);  } -#if 0 /* no user at this point */ -static void rcar_thermal_write(struct rcar_thermal_priv *priv, -			       u32 reg, u32 data) +#define rcar_thermal_read(p, r) _rcar_thermal_read(p, REG_ ##r) +static u32 _rcar_thermal_read(struct rcar_thermal_priv *priv, u32 reg)  { -	unsigned long flags; - -	spin_lock_irqsave(&priv->lock, flags); +	return ioread32(priv->base + reg); +} +#define rcar_thermal_write(p, r, d) _rcar_thermal_write(p, REG_ ##r, d) +static void _rcar_thermal_write(struct rcar_thermal_priv *priv, +				u32 reg, u32 data) +{  	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) +#define rcar_thermal_bset(p, r, m, d) _rcar_thermal_bset(p, REG_ ##r, m, d) +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) +static int rcar_thermal_update_temp(struct rcar_thermal_priv *priv)  { -	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); -	int val, min, max, tmp; +	struct device *dev = rcar_priv_to_dev(priv); +	int i; +	int ctemp, old, new; -	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; -		} +	mutex_lock(&priv->lock); -		/* -		 * 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; +	/* +	 * TSC decides a value of CPTAP automatically, +	 * and this is the conditions which validate interrupt. +	 */ +	rcar_thermal_bset(priv, THSCR, CPCTL, CPCTL); +	ctemp = 0; +	old = ~0; +	for (i = 0; i < 128; i++) {  		/*  		 * 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; +		new = rcar_thermal_read(priv, THSSR) & CTEMP; +		if (new == old) { +			ctemp = new; +			break; +		} +		old = new; +	} -		dev_dbg(priv->dev, "comp/min/max/val = %d/%d/%d/%d\n", -			priv->comp, min, max, val); +	if (!ctemp) { +		dev_err(dev, "thermal sensor was broken\n"); +		return -EINVAL; +	} -		/* -		 * 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; +	/* +	 * enable IRQ +	 */ +	if (rcar_has_irq_support(priv)) { +		rcar_thermal_write(priv, FILONOFF, 0); -		if (val <= min) { -			tmp = min; -			priv->comp--; /* try again */ -		} else if (val >= max) { -			tmp = max; -			priv->comp++; /* try again */ -		} else { -			tmp = val; -			break; -		} +		/* enable Rising/Falling edge interrupt */ +		rcar_thermal_write(priv, POSNEG,  0x1); +		rcar_thermal_write(priv, INTCTRL, (((ctemp - 0) << 8) | +						   ((ctemp - 1) << 0))); +	} + +	dev_dbg(dev, "thermal%d  %d -> %d\n", priv->id, priv->ctemp, ctemp); + +	priv->ctemp = ctemp; + +	mutex_unlock(&priv->lock); + +	return 0; +} + +static int rcar_thermal_get_temp(struct thermal_zone_device *zone, +				 unsigned long *temp) +{ +	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); + +	if (!rcar_has_irq_support(priv) || rcar_force_update_temp(priv)) +		rcar_thermal_update_temp(priv); + +	mutex_lock(&priv->lock); +	*temp =  MCELSIUS((priv->ctemp * 5) - 65); +	mutex_unlock(&priv->lock); + +	return 0; +} + +static int rcar_thermal_get_trip_type(struct thermal_zone_device *zone, +				      int trip, enum thermal_trip_type *type) +{ +	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); +	struct device *dev = rcar_priv_to_dev(priv); + +	/* see rcar_thermal_get_temp() */ +	switch (trip) { +	case 0: /* +90 <= temp */ +		*type = THERMAL_TRIP_CRITICAL; +		break; +	default: +		dev_err(dev, "rcar driver trip error\n"); +		return -EINVAL; +	} + +	return 0; +} + +static int rcar_thermal_get_trip_temp(struct thermal_zone_device *zone, +				      int trip, unsigned long *temp) +{ +	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); +	struct device *dev = rcar_priv_to_dev(priv); + +	/* see rcar_thermal_get_temp() */ +	switch (trip) { +	case 0: /* +90 <= temp */ +		*temp = MCELSIUS(90); +		break; +	default: +		dev_err(dev, "rcar driver trip error\n"); +		return -EINVAL; +	} + +	return 0; +} + +static int rcar_thermal_notify(struct thermal_zone_device *zone, +			       int trip, enum thermal_trip_type type) +{ +	struct rcar_thermal_priv *priv = rcar_zone_to_priv(zone); +	struct device *dev = rcar_priv_to_dev(priv); + +	switch (type) { +	case THERMAL_TRIP_CRITICAL: +		/* FIXME */ +		dev_warn(dev, "Thermal reached to critical temperature\n"); +		break; +	default: +		break;  	} -	*temp = MCELSIUS(tmp);  	return 0;  }  static struct thermal_zone_device_ops rcar_thermal_zone_ops = { -	.get_temp = rcar_thermal_get_temp, +	.get_temp	= rcar_thermal_get_temp, +	.get_trip_type	= rcar_thermal_get_trip_type, +	.get_trip_temp	= rcar_thermal_get_trip_temp, +	.notify		= rcar_thermal_notify,  };  /* - *		platform functions + *		interrupt   */ -static int rcar_thermal_probe(struct platform_device *pdev) +#define rcar_thermal_irq_enable(p)	_rcar_thermal_irq_ctrl(p, 1) +#define rcar_thermal_irq_disable(p)	_rcar_thermal_irq_ctrl(p, 0) +static void _rcar_thermal_irq_ctrl(struct rcar_thermal_priv *priv, int enable) +{ +	struct rcar_thermal_common *common = priv->common; +	unsigned long flags; +	u32 mask = 0x3 << rcar_id_to_shift(priv); /* enable Rising/Falling */ + +	spin_lock_irqsave(&common->lock, flags); + +	rcar_thermal_common_bset(common, INTMSK, mask, enable ? 0 : mask); + +	spin_unlock_irqrestore(&common->lock, flags); +} + +static void rcar_thermal_work(struct work_struct *work)  { -	struct thermal_zone_device *zone;  	struct rcar_thermal_priv *priv; -	struct resource *res; -	res = platform_get_resource(pdev, IORESOURCE_MEM, 0); -	if (!res) { -		dev_err(&pdev->dev, "Could not get platform resource\n"); -		return -ENODEV; +	priv = container_of(work, struct rcar_thermal_priv, work.work); + +	rcar_thermal_update_temp(priv); +	rcar_thermal_irq_enable(priv); +	thermal_zone_device_update(priv->zone); +} + +static u32 rcar_thermal_had_changed(struct rcar_thermal_priv *priv, u32 status) +{ +	struct device *dev = rcar_priv_to_dev(priv); + +	status = (status >> rcar_id_to_shift(priv)) & 0x3; + +	if (status & 0x3) { +		dev_dbg(dev, "thermal%d %s%s\n", +			priv->id, +			(status & 0x2) ? "Rising " : "", +			(status & 0x1) ? "Falling" : "");  	} -	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); -	if (!priv) { -		dev_err(&pdev->dev, "Could not allocate priv\n"); -		return -ENOMEM; +	return status; +} + +static irqreturn_t rcar_thermal_irq(int irq, void *data) +{ +	struct rcar_thermal_common *common = data; +	struct rcar_thermal_priv *priv; +	unsigned long flags; +	u32 status, mask; + +	spin_lock_irqsave(&common->lock, flags); + +	mask	= rcar_thermal_common_read(common, INTMSK); +	status	= rcar_thermal_common_read(common, STR); +	rcar_thermal_common_write(common, STR, 0x000F0F0F & mask); + +	spin_unlock_irqrestore(&common->lock, flags); + +	status = status & ~mask; + +	/* +	 * check the status +	 */ +	rcar_thermal_for_each_priv(priv, common) { +		if (rcar_thermal_had_changed(priv, status)) { +			rcar_thermal_irq_disable(priv); +			schedule_delayed_work(&priv->work, +					      msecs_to_jiffies(300)); +		}  	} -	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"); +	return IRQ_HANDLED; +} + +/* + *		platform functions + */ +static int rcar_thermal_probe(struct platform_device *pdev) +{ +	struct rcar_thermal_common *common; +	struct rcar_thermal_priv *priv; +	struct device *dev = &pdev->dev; +	struct resource *res, *irq; +	int mres = 0; +	int i; +	int idle = IDLE_INTERVAL; + +	common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL); +	if (!common) { +		dev_err(dev, "Could not allocate common\n");  		return -ENOMEM;  	} -	zone = thermal_zone_device_register("rcar_thermal", 0, 0, priv, -				    &rcar_thermal_zone_ops, NULL, 0, 0); -	if (IS_ERR(zone)) { -		dev_err(&pdev->dev, "thermal zone device is NULL\n"); -		return PTR_ERR(zone); +	INIT_LIST_HEAD(&common->head); +	spin_lock_init(&common->lock); +	common->dev = dev; + +	irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); +	if (irq) { +		int ret; + +		/* +		 * platform has IRQ support. +		 * Then, drier use common register +		 */ +		res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); +		if (!res) { +			dev_err(dev, "Could not get platform resource\n"); +			return -ENODEV; +		} + +		ret = devm_request_irq(dev, irq->start, rcar_thermal_irq, 0, +				       dev_name(dev), common); +		if (ret) { +			dev_err(dev, "irq request failed\n "); +			return ret; +		} + +		/* +		 * rcar_has_irq_support() will be enabled +		 */ +		common->base = devm_request_and_ioremap(dev, res); +		if (!common->base) { +			dev_err(dev, "Unable to ioremap thermal register\n"); +			return -ENOMEM; +		} + +		/* enable temperature comparation */ +		rcar_thermal_common_write(common, ENR, 0x00030303); + +		idle = 0; /* polling delaye is not needed */  	} -	platform_set_drvdata(pdev, zone); +	for (i = 0;; i++) { +		res = platform_get_resource(pdev, IORESOURCE_MEM, mres++); +		if (!res) +			break; + +		priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); +		if (!priv) { +			dev_err(dev, "Could not allocate priv\n"); +			return -ENOMEM; +		} + +		priv->base = devm_request_and_ioremap(dev, res); +		if (!priv->base) { +			dev_err(dev, "Unable to ioremap priv register\n"); +			return -ENOMEM; +		} + +		priv->common = common; +		priv->id = i; +		mutex_init(&priv->lock); +		INIT_LIST_HEAD(&priv->list); +		INIT_DELAYED_WORK(&priv->work, rcar_thermal_work); +		rcar_thermal_update_temp(priv); + +		priv->zone = thermal_zone_device_register("rcar_thermal", +						1, 0, priv, +						&rcar_thermal_zone_ops, NULL, 0, +						idle); +		if (IS_ERR(priv->zone)) { +			dev_err(dev, "can't register thermal zone\n"); +			goto error_unregister; +		} + +		list_move_tail(&priv->list, &common->head); -	dev_info(&pdev->dev, "proved\n"); +		if (rcar_has_irq_support(priv)) +			rcar_thermal_irq_enable(priv); +	} + +	platform_set_drvdata(pdev, common); + +	dev_info(dev, "%d sensor proved\n", i);  	return 0; + +error_unregister: +	rcar_thermal_for_each_priv(priv, common) +		thermal_zone_device_unregister(priv->zone); + +	return -ENODEV;  }  static int rcar_thermal_remove(struct platform_device *pdev)  { -	struct thermal_zone_device *zone = platform_get_drvdata(pdev); +	struct rcar_thermal_common *common = platform_get_drvdata(pdev); +	struct rcar_thermal_priv *priv; + +	rcar_thermal_for_each_priv(priv, common) +		thermal_zone_device_unregister(priv->zone); -	thermal_zone_device_unregister(zone);  	platform_set_drvdata(pdev, NULL);  	return 0;  } +static const struct of_device_id rcar_thermal_dt_ids[] = { +	{ .compatible = "renesas,rcar-thermal", }, +	{}, +}; +MODULE_DEVICE_TABLE(of, rcar_thermal_dt_ids); +  static struct platform_driver rcar_thermal_driver = {  	.driver	= {  		.name	= "rcar_thermal", +		.of_match_table = rcar_thermal_dt_ids,  	},  	.probe		= rcar_thermal_probe,  	.remove		= rcar_thermal_remove,  |