exynos_tmu.c 16.5 KB
Newer Older
D
Donggeun Kim 已提交
1
/*
2
 * exynos_tmu.c - Samsung EXYNOS TMU (Thermal Management Unit)
D
Donggeun Kim 已提交
3 4 5
 *
 *  Copyright (C) 2011 Samsung Electronics
 *  Donggeun Kim <dg77.kim@samsung.com>
6
 *  Amit Daniel Kachhap <amit.kachhap@linaro.org>
D
Donggeun Kim 已提交
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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/clk.h>
#include <linux/io.h>
26 27
#include <linux/interrupt.h>
#include <linux/module.h>
28
#include <linux/of.h>
29 30 31
#include <linux/platform_device.h>

#include "exynos_thermal_common.h"
32
#include "exynos_tmu.h"
33
#include "exynos_tmu_data.h"
34 35 36 37 38 39 40 41 42 43 44 45

/* Exynos generic registers */
#define EXYNOS_TMU_REG_TRIMINFO		0x0
#define EXYNOS_TMU_REG_CONTROL		0x20
#define EXYNOS_TMU_REG_STATUS		0x28
#define EXYNOS_TMU_REG_CURRENT_TEMP	0x40
#define EXYNOS_TMU_REG_INTEN		0x70
#define EXYNOS_TMU_REG_INTSTAT		0x74
#define EXYNOS_TMU_REG_INTCLEAR		0x78

#define EXYNOS_TMU_TRIM_TEMP_MASK	0xff
#define EXYNOS_TMU_GAIN_SHIFT		8
46
#define EXYNOS_TMU_GAIN_MASK		0xf
47
#define EXYNOS_TMU_REF_VOLTAGE_SHIFT	24
48 49 50 51
#define EXYNOS_TMU_REF_VOLTAGE_MASK	0x1f
#define EXYNOS_TMU_BUF_SLOPE_SEL_MASK	0xf
#define EXYNOS_TMU_BUF_SLOPE_SEL_SHIFT	8
#define EXYNOS_TMU_CORE_EN_SHIFT	0
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

/* Exynos4210 specific registers */
#define EXYNOS4210_TMU_REG_THRESHOLD_TEMP	0x44
#define EXYNOS4210_TMU_REG_TRIG_LEVEL0	0x50
#define EXYNOS4210_TMU_REG_TRIG_LEVEL1	0x54
#define EXYNOS4210_TMU_REG_TRIG_LEVEL2	0x58
#define EXYNOS4210_TMU_REG_TRIG_LEVEL3	0x5C
#define EXYNOS4210_TMU_REG_PAST_TEMP0	0x60
#define EXYNOS4210_TMU_REG_PAST_TEMP1	0x64
#define EXYNOS4210_TMU_REG_PAST_TEMP2	0x68
#define EXYNOS4210_TMU_REG_PAST_TEMP3	0x6C

#define EXYNOS4210_TMU_TRIG_LEVEL0_MASK	0x1
#define EXYNOS4210_TMU_TRIG_LEVEL1_MASK	0x10
#define EXYNOS4210_TMU_TRIG_LEVEL2_MASK	0x100
#define EXYNOS4210_TMU_TRIG_LEVEL3_MASK	0x1000
68
#define EXYNOS4210_TMU_TRIG_LEVEL_MASK	0x1111
69 70 71 72 73 74 75 76 77
#define EXYNOS4210_TMU_INTCLEAR_VAL	0x1111

/* Exynos5250 and Exynos4412 specific registers */
#define EXYNOS_TMU_TRIMINFO_CON	0x14
#define EXYNOS_THD_TEMP_RISE		0x50
#define EXYNOS_THD_TEMP_FALL		0x54
#define EXYNOS_EMUL_CON		0x80

#define EXYNOS_TRIMINFO_RELOAD		0x1
78 79 80 81 82
#define EXYNOS_TRIMINFO_SHIFT		0x0
#define EXYNOS_TMU_RISE_INT_MASK	0x111
#define EXYNOS_TMU_RISE_INT_SHIFT	0
#define EXYNOS_TMU_FALL_INT_MASK	0x111
#define EXYNOS_TMU_FALL_INT_SHIFT	12
83
#define EXYNOS_TMU_CLEAR_RISE_INT	0x111
84
#define EXYNOS_TMU_CLEAR_FALL_INT	(0x111 << 12)
85
#define EXYNOS_TMU_TRIP_MODE_SHIFT	13
86 87 88 89 90 91 92 93 94
#define EXYNOS_TMU_TRIP_MODE_MASK	0x7

#define EXYNOS_TMU_INTEN_RISE0_SHIFT	0
#define EXYNOS_TMU_INTEN_RISE1_SHIFT	4
#define EXYNOS_TMU_INTEN_RISE2_SHIFT	8
#define EXYNOS_TMU_INTEN_RISE3_SHIFT	12
#define EXYNOS_TMU_INTEN_FALL0_SHIFT	16
#define EXYNOS_TMU_INTEN_FALL1_SHIFT	20
#define EXYNOS_TMU_INTEN_FALL2_SHIFT	24
95

96
#ifdef CONFIG_THERMAL_EMULATION
97
#define EXYNOS_EMUL_TIME	0x57F0
98
#define EXYNOS_EMUL_TIME_MASK	0xffff
99 100 101 102
#define EXYNOS_EMUL_TIME_SHIFT	16
#define EXYNOS_EMUL_DATA_SHIFT	8
#define EXYNOS_EMUL_DATA_MASK	0xFF
#define EXYNOS_EMUL_ENABLE	0x1
103
#endif /* CONFIG_THERMAL_EMULATION */
104

105 106
struct exynos_tmu_data {
	struct exynos_tmu_platform_data *pdata;
D
Donggeun Kim 已提交
107 108 109
	struct resource *mem;
	void __iomem *base;
	int irq;
110
	enum soc_type soc;
D
Donggeun Kim 已提交
111 112 113 114 115 116 117 118 119 120
	struct work_struct irq_work;
	struct mutex lock;
	struct clk *clk;
	u8 temp_error1, temp_error2;
};

/*
 * TMU treats temperature as a mapped temperature code.
 * The temperature is converted differently depending on the calibration type.
 */
121
static int temp_to_code(struct exynos_tmu_data *data, u8 temp)
D
Donggeun Kim 已提交
122
{
123
	struct exynos_tmu_platform_data *pdata = data->pdata;
D
Donggeun Kim 已提交
124 125
	int temp_code;

126 127 128 129 130 131
	if (data->soc == SOC_ARCH_EXYNOS4210)
		/* temp should range between 25 and 125 */
		if (temp < 25 || temp > 125) {
			temp_code = -EINVAL;
			goto out;
		}
D
Donggeun Kim 已提交
132 133 134

	switch (pdata->cal_type) {
	case TYPE_TWO_POINT_TRIMMING:
135 136 137 138
		temp_code = (temp - pdata->first_point_trim) *
			(data->temp_error2 - data->temp_error1) /
			(pdata->second_point_trim - pdata->first_point_trim) +
			data->temp_error1;
D
Donggeun Kim 已提交
139 140
		break;
	case TYPE_ONE_POINT_TRIMMING:
141
		temp_code = temp + data->temp_error1 - pdata->first_point_trim;
D
Donggeun Kim 已提交
142 143
		break;
	default:
144
		temp_code = temp + pdata->default_temp_offset;
D
Donggeun Kim 已提交
145 146 147 148 149 150 151 152 153 154
		break;
	}
out:
	return temp_code;
}

/*
 * Calculate a temperature value from a temperature code.
 * The unit of the temperature is degree Celsius.
 */
155
static int code_to_temp(struct exynos_tmu_data *data, u8 temp_code)
D
Donggeun Kim 已提交
156
{
157
	struct exynos_tmu_platform_data *pdata = data->pdata;
D
Donggeun Kim 已提交
158 159
	int temp;

160 161 162 163 164 165
	if (data->soc == SOC_ARCH_EXYNOS4210)
		/* temp_code should range between 75 and 175 */
		if (temp_code < 75 || temp_code > 175) {
			temp = -ENODATA;
			goto out;
		}
D
Donggeun Kim 已提交
166 167 168

	switch (pdata->cal_type) {
	case TYPE_TWO_POINT_TRIMMING:
169 170 171 172
		temp = (temp_code - data->temp_error1) *
			(pdata->second_point_trim - pdata->first_point_trim) /
			(data->temp_error2 - data->temp_error1) +
			pdata->first_point_trim;
D
Donggeun Kim 已提交
173 174
		break;
	case TYPE_ONE_POINT_TRIMMING:
175
		temp = temp_code - data->temp_error1 + pdata->first_point_trim;
D
Donggeun Kim 已提交
176 177
		break;
	default:
178
		temp = temp_code - pdata->default_temp_offset;
D
Donggeun Kim 已提交
179 180 181 182 183 184
		break;
	}
out:
	return temp;
}

185
static int exynos_tmu_initialize(struct platform_device *pdev)
D
Donggeun Kim 已提交
186
{
187 188
	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
	struct exynos_tmu_platform_data *pdata = data->pdata;
189 190 191
	unsigned int status, trim_info;
	unsigned int rising_threshold = 0, falling_threshold = 0;
	int ret = 0, threshold_code, i, trigger_levs = 0;
D
Donggeun Kim 已提交
192 193 194 195

	mutex_lock(&data->lock);
	clk_enable(data->clk);

196
	status = readb(data->base + EXYNOS_TMU_REG_STATUS);
D
Donggeun Kim 已提交
197 198 199 200 201
	if (!status) {
		ret = -EBUSY;
		goto out;
	}

202 203 204 205
	if (data->soc == SOC_ARCH_EXYNOS) {
		__raw_writel(EXYNOS_TRIMINFO_RELOAD,
				data->base + EXYNOS_TMU_TRIMINFO_CON);
	}
D
Donggeun Kim 已提交
206
	/* Save trimming info in order to perform calibration */
207 208 209 210
	trim_info = readl(data->base + EXYNOS_TMU_REG_TRIMINFO);
	data->temp_error1 = trim_info & EXYNOS_TMU_TRIM_TEMP_MASK;
	data->temp_error2 = ((trim_info >> 8) & EXYNOS_TMU_TRIM_TEMP_MASK);

211 212
	if ((pdata->min_efuse_value > data->temp_error1) ||
			(data->temp_error1 > pdata->max_efuse_value) ||
213 214 215
			(data->temp_error2 != 0))
		data->temp_error1 = pdata->efuse_value;

216 217 218 219 220
	/* Count trigger levels to be enabled */
	for (i = 0; i < MAX_THRESHOLD_LEVS; i++)
		if (pdata->trigger_levels[i])
			trigger_levs++;

221 222 223 224 225 226 227 228 229
	if (data->soc == SOC_ARCH_EXYNOS4210) {
		/* Write temperature code for threshold */
		threshold_code = temp_to_code(data, pdata->threshold);
		if (threshold_code < 0) {
			ret = threshold_code;
			goto out;
		}
		writeb(threshold_code,
			data->base + EXYNOS4210_TMU_REG_THRESHOLD_TEMP);
230 231 232
		for (i = 0; i < trigger_levs; i++)
			writeb(pdata->trigger_levels[i],
			data->base + EXYNOS4210_TMU_REG_TRIG_LEVEL0 + i * 4);
233 234 235 236

		writel(EXYNOS4210_TMU_INTCLEAR_VAL,
			data->base + EXYNOS_TMU_REG_INTCLEAR);
	} else if (data->soc == SOC_ARCH_EXYNOS) {
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
		/* Write temperature code for rising and falling threshold */
		for (i = 0; i < trigger_levs; i++) {
			threshold_code = temp_to_code(data,
						pdata->trigger_levels[i]);
			if (threshold_code < 0) {
				ret = threshold_code;
				goto out;
			}
			rising_threshold |= threshold_code << 8 * i;
			if (pdata->threshold_falling) {
				threshold_code = temp_to_code(data,
						pdata->trigger_levels[i] -
						pdata->threshold_falling);
				if (threshold_code > 0)
					falling_threshold |=
						threshold_code << 8 * i;
			}
254 255 256 257
		}

		writel(rising_threshold,
				data->base + EXYNOS_THD_TEMP_RISE);
258 259
		writel(falling_threshold,
				data->base + EXYNOS_THD_TEMP_FALL);
260

261
		writel(EXYNOS_TMU_CLEAR_RISE_INT | EXYNOS_TMU_CLEAR_FALL_INT,
262
				data->base + EXYNOS_TMU_REG_INTCLEAR);
D
Donggeun Kim 已提交
263 264 265 266 267 268 269 270
	}
out:
	clk_disable(data->clk);
	mutex_unlock(&data->lock);

	return ret;
}

271
static void exynos_tmu_control(struct platform_device *pdev, bool on)
D
Donggeun Kim 已提交
272
{
273 274
	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
	struct exynos_tmu_platform_data *pdata = data->pdata;
D
Donggeun Kim 已提交
275 276 277 278 279
	unsigned int con, interrupt_en;

	mutex_lock(&data->lock);
	clk_enable(data->clk);

280
	con = readl(data->base + EXYNOS_TMU_REG_CONTROL);
281

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
	if (pdata->reference_voltage) {
		con &= ~(EXYNOS_TMU_REF_VOLTAGE_MASK <<
				EXYNOS_TMU_REF_VOLTAGE_SHIFT);
		con |= pdata->reference_voltage << EXYNOS_TMU_REF_VOLTAGE_SHIFT;
	}

	if (pdata->gain) {
		con &= ~(EXYNOS_TMU_GAIN_MASK << EXYNOS_TMU_GAIN_SHIFT);
		con |= (pdata->gain << EXYNOS_TMU_GAIN_SHIFT);
	}

	if (pdata->noise_cancel_mode) {
		con &= ~(EXYNOS_TMU_TRIP_MODE_MASK <<
					EXYNOS_TMU_TRIP_MODE_SHIFT);
		con |= (pdata->noise_cancel_mode << EXYNOS_TMU_TRIP_MODE_SHIFT);
297 298
	}

D
Donggeun Kim 已提交
299
	if (on) {
300 301
		con |= (1 << EXYNOS_TMU_CORE_EN_SHIFT);
		interrupt_en =
302 303 304 305
		pdata->trigger_enable[3] << EXYNOS_TMU_INTEN_RISE3_SHIFT |
		pdata->trigger_enable[2] << EXYNOS_TMU_INTEN_RISE2_SHIFT |
		pdata->trigger_enable[1] << EXYNOS_TMU_INTEN_RISE1_SHIFT |
		pdata->trigger_enable[0] << EXYNOS_TMU_INTEN_RISE0_SHIFT;
306
		if (pdata->threshold_falling)
307 308
			interrupt_en |=
				interrupt_en << EXYNOS_TMU_INTEN_FALL0_SHIFT;
D
Donggeun Kim 已提交
309
	} else {
310
		con &= ~(1 << EXYNOS_TMU_CORE_EN_SHIFT);
D
Donggeun Kim 已提交
311 312
		interrupt_en = 0; /* Disable all interrupts */
	}
313 314
	writel(interrupt_en, data->base + EXYNOS_TMU_REG_INTEN);
	writel(con, data->base + EXYNOS_TMU_REG_CONTROL);
D
Donggeun Kim 已提交
315 316 317 318 319

	clk_disable(data->clk);
	mutex_unlock(&data->lock);
}

320
static int exynos_tmu_read(struct exynos_tmu_data *data)
D
Donggeun Kim 已提交
321 322 323 324 325 326 327
{
	u8 temp_code;
	int temp;

	mutex_lock(&data->lock);
	clk_enable(data->clk);

328
	temp_code = readb(data->base + EXYNOS_TMU_REG_CURRENT_TEMP);
D
Donggeun Kim 已提交
329 330 331 332 333 334 335 336
	temp = code_to_temp(data, temp_code);

	clk_disable(data->clk);
	mutex_unlock(&data->lock);

	return temp;
}

337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377
#ifdef CONFIG_THERMAL_EMULATION
static int exynos_tmu_set_emulation(void *drv_data, unsigned long temp)
{
	struct exynos_tmu_data *data = drv_data;
	unsigned int reg;
	int ret = -EINVAL;

	if (data->soc == SOC_ARCH_EXYNOS4210)
		goto out;

	if (temp && temp < MCELSIUS)
		goto out;

	mutex_lock(&data->lock);
	clk_enable(data->clk);

	reg = readl(data->base + EXYNOS_EMUL_CON);

	if (temp) {
		temp /= MCELSIUS;

		reg = (EXYNOS_EMUL_TIME << EXYNOS_EMUL_TIME_SHIFT) |
			(temp_to_code(data, temp)
			 << EXYNOS_EMUL_DATA_SHIFT) | EXYNOS_EMUL_ENABLE;
	} else {
		reg &= ~EXYNOS_EMUL_ENABLE;
	}

	writel(reg, data->base + EXYNOS_EMUL_CON);

	clk_disable(data->clk);
	mutex_unlock(&data->lock);
	return 0;
out:
	return ret;
}
#else
static int exynos_tmu_set_emulation(void *drv_data,	unsigned long temp)
	{ return -EINVAL; }
#endif/*CONFIG_THERMAL_EMULATION*/

378
static void exynos_tmu_work(struct work_struct *work)
D
Donggeun Kim 已提交
379
{
380 381
	struct exynos_tmu_data *data = container_of(work,
			struct exynos_tmu_data, irq_work);
D
Donggeun Kim 已提交
382

383
	exynos_report_trigger();
D
Donggeun Kim 已提交
384 385
	mutex_lock(&data->lock);
	clk_enable(data->clk);
386
	if (data->soc == SOC_ARCH_EXYNOS)
387 388
		writel(EXYNOS_TMU_CLEAR_RISE_INT |
				EXYNOS_TMU_CLEAR_FALL_INT,
389 390 391 392
				data->base + EXYNOS_TMU_REG_INTCLEAR);
	else
		writel(EXYNOS4210_TMU_INTCLEAR_VAL,
				data->base + EXYNOS_TMU_REG_INTCLEAR);
D
Donggeun Kim 已提交
393 394
	clk_disable(data->clk);
	mutex_unlock(&data->lock);
395

396
	enable_irq(data->irq);
D
Donggeun Kim 已提交
397 398
}

399
static irqreturn_t exynos_tmu_irq(int irq, void *id)
D
Donggeun Kim 已提交
400
{
401
	struct exynos_tmu_data *data = id;
D
Donggeun Kim 已提交
402 403 404 405 406 407

	disable_irq_nosync(irq);
	schedule_work(&data->irq_work);

	return IRQ_HANDLED;
}
408 409 410
static struct thermal_sensor_conf exynos_sensor_conf = {
	.name			= "exynos-therm",
	.read_temperature	= (int (*)(void *))exynos_tmu_read,
411
	.write_emul_temp	= exynos_tmu_set_emulation,
412 413 414 415 416 417 418 419
};

#ifdef CONFIG_OF
static const struct of_device_id exynos_tmu_match[] = {
	{
		.compatible = "samsung,exynos4210-tmu",
		.data = (void *)EXYNOS4210_TMU_DRV_DATA,
	},
420 421
	{
		.compatible = "samsung,exynos4412-tmu",
422
		.data = (void *)EXYNOS5250_TMU_DRV_DATA,
423
	},
424 425
	{
		.compatible = "samsung,exynos5250-tmu",
426
		.data = (void *)EXYNOS5250_TMU_DRV_DATA,
427 428 429 430 431 432 433 434 435 436 437 438 439
	},
	{},
};
MODULE_DEVICE_TABLE(of, exynos_tmu_match);
#endif

static struct platform_device_id exynos_tmu_driver_ids[] = {
	{
		.name		= "exynos4210-tmu",
		.driver_data    = (kernel_ulong_t)EXYNOS4210_TMU_DRV_DATA,
	},
	{
		.name		= "exynos5250-tmu",
440
		.driver_data    = (kernel_ulong_t)EXYNOS5250_TMU_DRV_DATA,
441 442 443
	},
	{ },
};
444
MODULE_DEVICE_TABLE(platform, exynos_tmu_driver_ids);
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459

static inline struct  exynos_tmu_platform_data *exynos_get_driver_data(
			struct platform_device *pdev)
{
#ifdef CONFIG_OF
	if (pdev->dev.of_node) {
		const struct of_device_id *match;
		match = of_match_node(exynos_tmu_match, pdev->dev.of_node);
		if (!match)
			return NULL;
		return (struct exynos_tmu_platform_data *) match->data;
	}
#endif
	return (struct exynos_tmu_platform_data *)
			platform_get_device_id(pdev)->driver_data;
460
}
461

462
static int exynos_tmu_probe(struct platform_device *pdev)
D
Donggeun Kim 已提交
463
{
464 465
	struct exynos_tmu_data *data;
	struct exynos_tmu_platform_data *pdata = pdev->dev.platform_data;
466
	int ret, i;
D
Donggeun Kim 已提交
467

468 469 470
	if (!pdata)
		pdata = exynos_get_driver_data(pdev);

D
Donggeun Kim 已提交
471 472 473 474
	if (!pdata) {
		dev_err(&pdev->dev, "No platform init data supplied.\n");
		return -ENODEV;
	}
475 476
	data = devm_kzalloc(&pdev->dev, sizeof(struct exynos_tmu_data),
					GFP_KERNEL);
D
Donggeun Kim 已提交
477 478 479 480 481 482 483 484
	if (!data) {
		dev_err(&pdev->dev, "Failed to allocate driver structure\n");
		return -ENOMEM;
	}

	data->irq = platform_get_irq(pdev, 0);
	if (data->irq < 0) {
		dev_err(&pdev->dev, "Failed to get platform irq\n");
485
		return data->irq;
D
Donggeun Kim 已提交
486 487
	}

488
	INIT_WORK(&data->irq_work, exynos_tmu_work);
D
Donggeun Kim 已提交
489 490

	data->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
491 492 493
	data->base = devm_ioremap_resource(&pdev->dev, data->mem);
	if (IS_ERR(data->base))
		return PTR_ERR(data->base);
D
Donggeun Kim 已提交
494

495
	ret = devm_request_irq(&pdev->dev, data->irq, exynos_tmu_irq,
496
		IRQF_TRIGGER_RISING, "exynos-tmu", data);
D
Donggeun Kim 已提交
497 498
	if (ret) {
		dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
499
		return ret;
D
Donggeun Kim 已提交
500 501
	}

502
	data->clk = devm_clk_get(&pdev->dev, "tmu_apbif");
D
Donggeun Kim 已提交
503 504
	if (IS_ERR(data->clk)) {
		dev_err(&pdev->dev, "Failed to get clock\n");
505
		return  PTR_ERR(data->clk);
D
Donggeun Kim 已提交
506 507
	}

508 509 510 511
	ret = clk_prepare(data->clk);
	if (ret)
		return ret;

512 513 514 515 516 517 518 519 520
	if (pdata->type == SOC_ARCH_EXYNOS ||
				pdata->type == SOC_ARCH_EXYNOS4210)
		data->soc = pdata->type;
	else {
		ret = -EINVAL;
		dev_err(&pdev->dev, "Platform not supported\n");
		goto err_clk;
	}

D
Donggeun Kim 已提交
521 522 523 524
	data->pdata = pdata;
	platform_set_drvdata(pdev, data);
	mutex_init(&data->lock);

525
	ret = exynos_tmu_initialize(pdev);
D
Donggeun Kim 已提交
526 527 528 529 530
	if (ret) {
		dev_err(&pdev->dev, "Failed to initialize TMU\n");
		goto err_clk;
	}

531
	exynos_tmu_control(pdev, true);
D
Donggeun Kim 已提交
532

533 534
	/* Register the sensor with thermal management interface */
	(&exynos_sensor_conf)->private_data = data;
535 536 537
	exynos_sensor_conf.trip_data.trip_count = pdata->trigger_enable[0] +
			pdata->trigger_enable[1] + pdata->trigger_enable[2]+
			pdata->trigger_enable[3];
538 539 540 541 542

	for (i = 0; i < exynos_sensor_conf.trip_data.trip_count; i++)
		exynos_sensor_conf.trip_data.trip_val[i] =
			pdata->threshold + pdata->trigger_levels[i];

543 544
	exynos_sensor_conf.trip_data.trigger_falling = pdata->threshold_falling;

545 546 547 548 549 550 551 552 553 554 555 556 557 558
	exynos_sensor_conf.cooling_data.freq_clip_count =
						pdata->freq_tab_count;
	for (i = 0; i < pdata->freq_tab_count; i++) {
		exynos_sensor_conf.cooling_data.freq_data[i].freq_clip_max =
					pdata->freq_tab[i].freq_clip_max;
		exynos_sensor_conf.cooling_data.freq_data[i].temp_level =
					pdata->freq_tab[i].temp_level;
	}

	ret = exynos_register_thermal(&exynos_sensor_conf);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register thermal interface\n");
		goto err_clk;
	}
559

D
Donggeun Kim 已提交
560 561
	return 0;
err_clk:
562
	clk_unprepare(data->clk);
D
Donggeun Kim 已提交
563 564 565
	return ret;
}

566
static int exynos_tmu_remove(struct platform_device *pdev)
D
Donggeun Kim 已提交
567
{
568
	struct exynos_tmu_data *data = platform_get_drvdata(pdev);
D
Donggeun Kim 已提交
569

570
	exynos_tmu_control(pdev, false);
D
Donggeun Kim 已提交
571

572 573
	exynos_unregister_thermal();

574
	clk_unprepare(data->clk);
D
Donggeun Kim 已提交
575 576 577 578

	return 0;
}

579
#ifdef CONFIG_PM_SLEEP
580
static int exynos_tmu_suspend(struct device *dev)
D
Donggeun Kim 已提交
581
{
582
	exynos_tmu_control(to_platform_device(dev), false);
D
Donggeun Kim 已提交
583 584 585 586

	return 0;
}

587
static int exynos_tmu_resume(struct device *dev)
D
Donggeun Kim 已提交
588
{
589 590
	struct platform_device *pdev = to_platform_device(dev);

591 592
	exynos_tmu_initialize(pdev);
	exynos_tmu_control(pdev, true);
D
Donggeun Kim 已提交
593 594 595

	return 0;
}
596

597 598 599
static SIMPLE_DEV_PM_OPS(exynos_tmu_pm,
			 exynos_tmu_suspend, exynos_tmu_resume);
#define EXYNOS_TMU_PM	(&exynos_tmu_pm)
D
Donggeun Kim 已提交
600
#else
601
#define EXYNOS_TMU_PM	NULL
D
Donggeun Kim 已提交
602 603
#endif

604
static struct platform_driver exynos_tmu_driver = {
D
Donggeun Kim 已提交
605
	.driver = {
606
		.name   = "exynos-tmu",
D
Donggeun Kim 已提交
607
		.owner  = THIS_MODULE,
608
		.pm     = EXYNOS_TMU_PM,
609
		.of_match_table = of_match_ptr(exynos_tmu_match),
D
Donggeun Kim 已提交
610
	},
611
	.probe = exynos_tmu_probe,
612
	.remove	= exynos_tmu_remove,
613
	.id_table = exynos_tmu_driver_ids,
D
Donggeun Kim 已提交
614 615
};

616
module_platform_driver(exynos_tmu_driver);
D
Donggeun Kim 已提交
617

618
MODULE_DESCRIPTION("EXYNOS TMU Driver");
D
Donggeun Kim 已提交
619 620
MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
MODULE_LICENSE("GPL");
621
MODULE_ALIAS("platform:exynos-tmu");