293 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2015, The Linux Foundation. All rights reserved.
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License version 2 and
 | |
|  * only version 2 as published by the Free Software Foundation.
 | |
|  *
 | |
|  * 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 <linux/platform_device.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/bitops.h>
 | |
| #include <linux/regmap.h>
 | |
| #include <linux/thermal.h>
 | |
| #include "tsens.h"
 | |
| 
 | |
| #define CAL_MDEGC		30000
 | |
| 
 | |
| #define CONFIG_ADDR		0x3640
 | |
| #define CONFIG_ADDR_8660	0x3620
 | |
| /* CONFIG_ADDR bitmasks */
 | |
| #define CONFIG			0x9b
 | |
| #define CONFIG_MASK		0xf
 | |
| #define CONFIG_8660		1
 | |
| #define CONFIG_SHIFT_8660	28
 | |
| #define CONFIG_MASK_8660	(3 << CONFIG_SHIFT_8660)
 | |
| 
 | |
| #define STATUS_CNTL_ADDR_8064	0x3660
 | |
| #define CNTL_ADDR		0x3620
 | |
| /* CNTL_ADDR bitmasks */
 | |
| #define EN			BIT(0)
 | |
| #define SW_RST			BIT(1)
 | |
| #define SENSOR0_EN		BIT(3)
 | |
| #define SLP_CLK_ENA		BIT(26)
 | |
| #define SLP_CLK_ENA_8660	BIT(24)
 | |
| #define MEASURE_PERIOD		1
 | |
| #define SENSOR0_SHIFT		3
 | |
| 
 | |
| /* INT_STATUS_ADDR bitmasks */
 | |
| #define MIN_STATUS_MASK		BIT(0)
 | |
| #define LOWER_STATUS_CLR	BIT(1)
 | |
| #define UPPER_STATUS_CLR	BIT(2)
 | |
| #define MAX_STATUS_MASK		BIT(3)
 | |
| 
 | |
| #define THRESHOLD_ADDR		0x3624
 | |
| /* THRESHOLD_ADDR bitmasks */
 | |
| #define THRESHOLD_MAX_LIMIT_SHIFT	24
 | |
| #define THRESHOLD_MIN_LIMIT_SHIFT	16
 | |
| #define THRESHOLD_UPPER_LIMIT_SHIFT	8
 | |
| #define THRESHOLD_LOWER_LIMIT_SHIFT	0
 | |
| 
 | |
| /* Initial temperature threshold values */
 | |
| #define LOWER_LIMIT_TH		0x50
 | |
| #define UPPER_LIMIT_TH		0xdf
 | |
| #define MIN_LIMIT_TH		0x0
 | |
| #define MAX_LIMIT_TH		0xff
 | |
| 
 | |
| #define S0_STATUS_ADDR		0x3628
 | |
| #define INT_STATUS_ADDR		0x363c
 | |
| #define TRDY_MASK		BIT(7)
 | |
| #define TIMEOUT_US		100
 | |
| 
 | |
| static int suspend_8960(struct tsens_device *tmdev)
 | |
| {
 | |
| 	int ret;
 | |
| 	unsigned int mask;
 | |
| 	struct regmap *map = tmdev->map;
 | |
| 
 | |
| 	ret = regmap_read(map, THRESHOLD_ADDR, &tmdev->ctx.threshold);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = regmap_read(map, CNTL_ADDR, &tmdev->ctx.control);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (tmdev->num_sensors > 1)
 | |
| 		mask = SLP_CLK_ENA | EN;
 | |
| 	else
 | |
| 		mask = SLP_CLK_ENA_8660 | EN;
 | |
| 
 | |
| 	ret = regmap_update_bits(map, CNTL_ADDR, mask, 0);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int resume_8960(struct tsens_device *tmdev)
 | |
| {
 | |
| 	int ret;
 | |
| 	struct regmap *map = tmdev->map;
 | |
| 
 | |
| 	ret = regmap_update_bits(map, CNTL_ADDR, SW_RST, SW_RST);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/*
 | |
| 	 * Separate CONFIG restore is not needed only for 8660 as
 | |
| 	 * config is part of CTRL Addr and its restored as such
 | |
| 	 */
 | |
| 	if (tmdev->num_sensors > 1) {
 | |
| 		ret = regmap_update_bits(map, CONFIG_ADDR, CONFIG_MASK, CONFIG);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = regmap_write(map, THRESHOLD_ADDR, tmdev->ctx.threshold);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = regmap_write(map, CNTL_ADDR, tmdev->ctx.control);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int enable_8960(struct tsens_device *tmdev, int id)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 reg, mask;
 | |
| 
 | |
| 	ret = regmap_read(tmdev->map, CNTL_ADDR, ®);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	mask = BIT(id + SENSOR0_SHIFT);
 | |
| 	ret = regmap_write(tmdev->map, CNTL_ADDR, reg | SW_RST);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (tmdev->num_sensors > 1)
 | |
| 		reg |= mask | SLP_CLK_ENA | EN;
 | |
| 	else
 | |
| 		reg |= mask | SLP_CLK_ENA_8660 | EN;
 | |
| 
 | |
| 	ret = regmap_write(tmdev->map, CNTL_ADDR, reg);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void disable_8960(struct tsens_device *tmdev)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 reg_cntl;
 | |
| 	u32 mask;
 | |
| 
 | |
| 	mask = GENMASK(tmdev->num_sensors - 1, 0);
 | |
| 	mask <<= SENSOR0_SHIFT;
 | |
| 	mask |= EN;
 | |
| 
 | |
| 	ret = regmap_read(tmdev->map, CNTL_ADDR, ®_cntl);
 | |
| 	if (ret)
 | |
| 		return;
 | |
| 
 | |
| 	reg_cntl &= ~mask;
 | |
| 
 | |
| 	if (tmdev->num_sensors > 1)
 | |
| 		reg_cntl &= ~SLP_CLK_ENA;
 | |
| 	else
 | |
| 		reg_cntl &= ~SLP_CLK_ENA_8660;
 | |
| 
 | |
| 	regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
 | |
| }
 | |
| 
 | |
| static int init_8960(struct tsens_device *tmdev)
 | |
| {
 | |
| 	int ret, i;
 | |
| 	u32 reg_cntl;
 | |
| 
 | |
| 	tmdev->map = dev_get_regmap(tmdev->dev, NULL);
 | |
| 	if (!tmdev->map)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	/*
 | |
| 	 * The status registers for each sensor are discontiguous
 | |
| 	 * because some SoCs have 5 sensors while others have more
 | |
| 	 * but the control registers stay in the same place, i.e
 | |
| 	 * directly after the first 5 status registers.
 | |
| 	 */
 | |
| 	for (i = 0; i < tmdev->num_sensors; i++) {
 | |
| 		if (i >= 5)
 | |
| 			tmdev->sensor[i].status = S0_STATUS_ADDR + 40;
 | |
| 		tmdev->sensor[i].status += i * 4;
 | |
| 	}
 | |
| 
 | |
| 	reg_cntl = SW_RST;
 | |
| 	ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	if (tmdev->num_sensors > 1) {
 | |
| 		reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18);
 | |
| 		reg_cntl &= ~SW_RST;
 | |
| 		ret = regmap_update_bits(tmdev->map, CONFIG_ADDR,
 | |
| 					 CONFIG_MASK, CONFIG);
 | |
| 	} else {
 | |
| 		reg_cntl |= SLP_CLK_ENA_8660 | (MEASURE_PERIOD << 16);
 | |
| 		reg_cntl &= ~CONFIG_MASK_8660;
 | |
| 		reg_cntl |= CONFIG_8660 << CONFIG_SHIFT_8660;
 | |
| 	}
 | |
| 
 | |
| 	reg_cntl |= GENMASK(tmdev->num_sensors - 1, 0) << SENSOR0_SHIFT;
 | |
| 	ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	reg_cntl |= EN;
 | |
| 	ret = regmap_write(tmdev->map, CNTL_ADDR, reg_cntl);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int calibrate_8960(struct tsens_device *tmdev)
 | |
| {
 | |
| 	int i;
 | |
| 	char *data;
 | |
| 
 | |
| 	ssize_t num_read = tmdev->num_sensors;
 | |
| 	struct tsens_sensor *s = tmdev->sensor;
 | |
| 
 | |
| 	data = qfprom_read(tmdev->dev, "calib");
 | |
| 	if (IS_ERR(data))
 | |
| 		data = qfprom_read(tmdev->dev, "calib_backup");
 | |
| 	if (IS_ERR(data))
 | |
| 		return PTR_ERR(data);
 | |
| 
 | |
| 	for (i = 0; i < num_read; i++, s++)
 | |
| 		s->offset = data[i];
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* Temperature on y axis and ADC-code on x-axis */
 | |
| static inline int code_to_mdegC(u32 adc_code, const struct tsens_sensor *s)
 | |
| {
 | |
| 	int slope, offset;
 | |
| 
 | |
| 	slope = thermal_zone_get_slope(s->tzd);
 | |
| 	offset = CAL_MDEGC - slope * s->offset;
 | |
| 
 | |
| 	return adc_code * slope + offset;
 | |
| }
 | |
| 
 | |
| static int get_temp_8960(struct tsens_device *tmdev, int id, int *temp)
 | |
| {
 | |
| 	int ret;
 | |
| 	u32 code, trdy;
 | |
| 	const struct tsens_sensor *s = &tmdev->sensor[id];
 | |
| 	unsigned long timeout;
 | |
| 
 | |
| 	timeout = jiffies + usecs_to_jiffies(TIMEOUT_US);
 | |
| 	do {
 | |
| 		ret = regmap_read(tmdev->map, INT_STATUS_ADDR, &trdy);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 		if (!(trdy & TRDY_MASK))
 | |
| 			continue;
 | |
| 		ret = regmap_read(tmdev->map, s->status, &code);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 		*temp = code_to_mdegC(code, s);
 | |
| 		return 0;
 | |
| 	} while (time_before(jiffies, timeout));
 | |
| 
 | |
| 	return -ETIMEDOUT;
 | |
| }
 | |
| 
 | |
| static const struct tsens_ops ops_8960 = {
 | |
| 	.init		= init_8960,
 | |
| 	.calibrate	= calibrate_8960,
 | |
| 	.get_temp	= get_temp_8960,
 | |
| 	.enable		= enable_8960,
 | |
| 	.disable	= disable_8960,
 | |
| 	.suspend	= suspend_8960,
 | |
| 	.resume		= resume_8960,
 | |
| };
 | |
| 
 | |
| const struct tsens_data data_8960 = {
 | |
| 	.num_sensors	= 11,
 | |
| 	.ops		= &ops_8960,
 | |
| };
 | 
