mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
4a26cc02e3
Dereference the thermal device pointer only after checking that it is valid. Change-Id: I5619b14f2d37969e59a16a64b6011c1dba9b2e15 Signed-off-by: Stepan Moskovchenko <stepanm@codeaurora.org>
885 lines
26 KiB
C
885 lines
26 KiB
C
/* Copyright (c) 2012, Code Aurora Forum. 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/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/io.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/msm_tsens.h>
|
|
#include <linux/err.h>
|
|
#include <linux/of.h>
|
|
|
|
#include <mach/msm_iomap.h>
|
|
|
|
#define TSENS_DRIVER_NAME "msm-tsens"
|
|
/* TSENS register info */
|
|
#define TSENS_UPPER_LOWER_INTERRUPT_CTRL(n) ((n) + 0x1000)
|
|
#define TSENS_INTERRUPT_EN BIT(0)
|
|
|
|
#define TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR(n) ((n) + 0x1004)
|
|
#define TSENS_UPPER_STATUS_CLR BIT(21)
|
|
#define TSENS_LOWER_STATUS_CLR BIT(20)
|
|
#define TSENS_UPPER_THRESHOLD_MASK 0xffc00
|
|
#define TSENS_LOWER_THRESHOLD_MASK 0x3ff
|
|
#define TSENS_UPPER_THRESHOLD_SHIFT 10
|
|
|
|
#define TSENS_S0_STATUS_ADDR(n) ((n) + 0x1030)
|
|
#define TSENS_SN_ADDR_OFFSET 0x4
|
|
#define TSENS_SN_STATUS_TEMP_MASK 0x3ff
|
|
#define TSENS_SN_STATUS_LOWER_STATUS BIT(11)
|
|
#define TSENS_SN_STATUS_UPPER_STATUS BIT(12)
|
|
#define TSENS_STATUS_ADDR_OFFSET 2
|
|
|
|
#define TSENS_TRDY_ADDR(n) ((n) + 0x105c)
|
|
#define TSENS_TRDY_MASK BIT(0)
|
|
|
|
#define TSENS_CTRL_ADDR(n) (n)
|
|
#define TSENS_SW_RST BIT(1)
|
|
#define TSENS_SN_MIN_MAX_STATUS_CTRL(n) ((n) + 4)
|
|
#define TSENS_GLOBAL_CONFIG(n) ((n) + 0x34)
|
|
#define TSENS_S0_MAIN_CONFIG(n) ((n) + 0x38)
|
|
#define TSENS_SN_REMOTE_CONFIG(n) ((n) + 0x3c)
|
|
|
|
/* TSENS calibration Mask data */
|
|
#define TSENS_BASE1_MASK 0xff
|
|
#define TSENS0_POINT1_MASK 0x3f00
|
|
#define TSENS1_POINT1_MASK 0xfc000
|
|
#define TSENS2_POINT1_MASK 0x3f00000
|
|
#define TSENS3_POINT1_MASK 0xfc000000
|
|
#define TSENS4_POINT1_MASK 0x3f
|
|
#define TSENS5_POINT1_MASK 0xfc00
|
|
#define TSENS6_POINT1_MASK 0x3f000
|
|
#define TSENS7_POINT1_MASK 0xfc0000
|
|
#define TSENS8_POINT1_MASK 0x3f000000
|
|
#define TSENS9_POINT1_MASK 0x3f
|
|
#define TSENS10_POINT1_MASK 0xfc00
|
|
#define TSENS_CAL_SEL_0_1 0xc0000000
|
|
#define TSENS_CAL_SEL_2 BIT(30)
|
|
#define TSENS_CAL_SEL_SHIFT 30
|
|
#define TSENS_CAL_SEL_SHIFT_2 28
|
|
#define TSENS_ONE_POINT_CALIB 0x3
|
|
#define TSENS_TWO_POINT_CALIB 0x2
|
|
|
|
#define TSENS_BASE2_MASK 0xff000
|
|
#define TSENS0_POINT2_MASK 0x3f00000
|
|
#define TSENS1_POINT2_MASK 0xfc000000
|
|
#define TSENS2_POINT2_MASK 0x3f
|
|
#define TSENS3_POINT2_MASK 0xfc00
|
|
#define TSENS4_POINT2_MASK 0x3f000
|
|
#define TSENS5_POINT2_MASK 0xfc0000
|
|
#define TSENS6_POINT2_MASK 0x3f000000
|
|
#define TSENS7_POINT2_MASK 0x3f
|
|
#define TSENS8_POINT2_MASK 0xfc00
|
|
#define TSENS9_POINT2_MASK 0x3f000
|
|
#define TSENS10_POINT2_MASK 0xfc0000
|
|
|
|
#define TSENS_BIT_APPEND 0x3
|
|
#define TSENS_CAL_DEGC_POINT1 30
|
|
#define TSENS_CAL_DEGC_POINT2 120
|
|
#define TSENS_SLOPE_FACTOR 1000
|
|
|
|
/* TSENS register data */
|
|
#define TSENS_TRDY_RDY_MIN_TIME 2000
|
|
#define TSENS_TRDY_RDY_MAX_TIME 2100
|
|
#define TSENS_THRESHOLD_MAX_CODE 0x3ff
|
|
#define TSENS_THRESHOLD_MIN_CODE 0x0
|
|
|
|
#define TSENS_CTRL_INIT_DATA1 0x3fffff9
|
|
#define TSENS_GLOBAL_INIT_DATA 0x302f16c
|
|
#define TSENS_S0_MAIN_CFG_INIT_DATA 0x1c3
|
|
#define TSENS_SN_MIN_MAX_STATUS_CTRL_DATA 0x3ffc00
|
|
#define TSENS_SN_REMOTE_CFG_DATA 0x11c3
|
|
|
|
/* Trips: warm and cool */
|
|
enum tsens_trip_type {
|
|
TSENS_TRIP_WARM = 0,
|
|
TSENS_TRIP_COOL,
|
|
TSENS_TRIP_NUM,
|
|
};
|
|
|
|
struct tsens_tm_device_sensor {
|
|
struct thermal_zone_device *tz_dev;
|
|
enum thermal_device_mode mode;
|
|
unsigned int sensor_num;
|
|
struct work_struct work;
|
|
int offset;
|
|
int calib_data_point1;
|
|
int calib_data_point2;
|
|
uint32_t slope_mul_tsens_factor;
|
|
};
|
|
|
|
struct tsens_tm_device {
|
|
struct platform_device *pdev;
|
|
bool prev_reading_avail;
|
|
int tsens_factor;
|
|
uint32_t tsens_num_sensor;
|
|
int tsens_irq;
|
|
void *tsens_addr;
|
|
void *tsens_calib_addr;
|
|
int tsens_len;
|
|
int calib_len;
|
|
struct resource *res_tsens_mem;
|
|
struct resource *res_calib_mem;
|
|
struct tsens_tm_device_sensor sensor[0];
|
|
};
|
|
|
|
struct tsens_tm_device *tmdev;
|
|
|
|
static int tsens_tz_code_to_degc(int adc_code, int sensor_num)
|
|
{
|
|
int degcbeforefactor, degc;
|
|
degcbeforefactor = (adc_code *
|
|
tmdev->sensor[sensor_num].slope_mul_tsens_factor
|
|
+ tmdev->sensor[sensor_num].offset);
|
|
|
|
if (degcbeforefactor == 0)
|
|
degc = degcbeforefactor;
|
|
else if (degcbeforefactor > 0)
|
|
degc = (degcbeforefactor + tmdev->tsens_factor/2)
|
|
/ tmdev->tsens_factor;
|
|
else
|
|
degc = (degcbeforefactor - tmdev->tsens_factor/2)
|
|
/ tmdev->tsens_factor;
|
|
return degc;
|
|
}
|
|
|
|
static int tsens_tz_degc_to_code(int degc, int sensor_num)
|
|
{
|
|
int code = (degc * tmdev->tsens_factor -
|
|
tmdev->sensor[sensor_num].offset
|
|
+ tmdev->sensor[sensor_num].slope_mul_tsens_factor/2)
|
|
/ tmdev->sensor[sensor_num].slope_mul_tsens_factor;
|
|
|
|
if (code > TSENS_THRESHOLD_MAX_CODE)
|
|
code = TSENS_THRESHOLD_MAX_CODE;
|
|
else if (code < TSENS_THRESHOLD_MIN_CODE)
|
|
code = TSENS_THRESHOLD_MIN_CODE;
|
|
return code;
|
|
}
|
|
|
|
static void msm_tsens_get_temp(int sensor_num, unsigned long *temp)
|
|
{
|
|
unsigned int code, sensor_addr;
|
|
|
|
if (!tmdev->prev_reading_avail) {
|
|
while (!(readl_relaxed(TSENS_TRDY_ADDR(tmdev->tsens_addr))
|
|
& TSENS_TRDY_MASK))
|
|
usleep_range(TSENS_TRDY_RDY_MIN_TIME,
|
|
TSENS_TRDY_RDY_MAX_TIME);
|
|
tmdev->prev_reading_avail = true;
|
|
}
|
|
|
|
sensor_addr =
|
|
(unsigned int)TSENS_S0_STATUS_ADDR(tmdev->tsens_addr);
|
|
code = readl_relaxed(sensor_addr +
|
|
(sensor_num << TSENS_STATUS_ADDR_OFFSET));
|
|
*temp = tsens_tz_code_to_degc((code & TSENS_SN_STATUS_TEMP_MASK),
|
|
sensor_num);
|
|
}
|
|
|
|
static int tsens_tz_get_temp(struct thermal_zone_device *thermal,
|
|
unsigned long *temp)
|
|
{
|
|
struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
|
|
|
|
if (!tm_sensor || tm_sensor->mode != THERMAL_DEVICE_ENABLED || !temp)
|
|
return -EINVAL;
|
|
|
|
msm_tsens_get_temp(tm_sensor->sensor_num, temp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tsens_get_temp(struct tsens_device *device, unsigned long *temp)
|
|
{
|
|
if (!tmdev)
|
|
return -ENODEV;
|
|
|
|
msm_tsens_get_temp(device->sensor_num, temp);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tsens_get_temp);
|
|
|
|
static int tsens_tz_get_mode(struct thermal_zone_device *thermal,
|
|
enum thermal_device_mode *mode)
|
|
{
|
|
struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
|
|
|
|
if (!tm_sensor || !mode)
|
|
return -EINVAL;
|
|
|
|
*mode = tm_sensor->mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsens_tz_get_trip_type(struct thermal_zone_device *thermal,
|
|
int trip, enum thermal_trip_type *type)
|
|
{
|
|
struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
|
|
|
|
if (!tm_sensor || trip < 0 || !type)
|
|
return -EINVAL;
|
|
|
|
switch (trip) {
|
|
case TSENS_TRIP_WARM:
|
|
*type = THERMAL_TRIP_CONFIGURABLE_HI;
|
|
break;
|
|
case TSENS_TRIP_COOL:
|
|
*type = THERMAL_TRIP_CONFIGURABLE_LOW;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsens_tz_activate_trip_type(struct thermal_zone_device *thermal,
|
|
int trip, enum thermal_trip_activation_mode mode)
|
|
{
|
|
struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
|
|
unsigned int reg_cntl, code, hi_code, lo_code, mask;
|
|
|
|
if (!tm_sensor || trip < 0)
|
|
return -EINVAL;
|
|
|
|
lo_code = TSENS_THRESHOLD_MIN_CODE;
|
|
hi_code = TSENS_THRESHOLD_MAX_CODE;
|
|
|
|
reg_cntl = readl_relaxed((TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
|
|
(tmdev->tsens_addr) +
|
|
(tm_sensor->sensor_num * 4)));
|
|
switch (trip) {
|
|
case TSENS_TRIP_WARM:
|
|
code = (reg_cntl & TSENS_UPPER_THRESHOLD_MASK)
|
|
>> TSENS_UPPER_THRESHOLD_SHIFT;
|
|
mask = TSENS_UPPER_STATUS_CLR;
|
|
|
|
if (!(reg_cntl & TSENS_LOWER_STATUS_CLR))
|
|
lo_code = (reg_cntl & TSENS_LOWER_THRESHOLD_MASK);
|
|
break;
|
|
case TSENS_TRIP_COOL:
|
|
code = (reg_cntl & TSENS_LOWER_THRESHOLD_MASK);
|
|
mask = TSENS_LOWER_STATUS_CLR;
|
|
|
|
if (!(reg_cntl & TSENS_UPPER_STATUS_CLR))
|
|
hi_code = (reg_cntl & TSENS_UPPER_THRESHOLD_MASK)
|
|
>> TSENS_UPPER_THRESHOLD_SHIFT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (mode == THERMAL_TRIP_ACTIVATION_DISABLED)
|
|
writel_relaxed(reg_cntl | mask,
|
|
(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
|
|
(tmdev->tsens_addr) +
|
|
(tm_sensor->sensor_num * 4)));
|
|
else {
|
|
if (code < lo_code || code > hi_code) {
|
|
pr_err("%s with invalid code %x\n", __func__, code);
|
|
return -EINVAL;
|
|
}
|
|
writel_relaxed(reg_cntl & ~mask,
|
|
(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR(tmdev->tsens_addr) +
|
|
(tm_sensor->sensor_num * 4)));
|
|
}
|
|
mb();
|
|
return 0;
|
|
}
|
|
|
|
static int tsens_tz_get_trip_temp(struct thermal_zone_device *thermal,
|
|
int trip, unsigned long *temp)
|
|
{
|
|
struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
|
|
unsigned int reg;
|
|
|
|
if (!tm_sensor || trip < 0 || !temp)
|
|
return -EINVAL;
|
|
|
|
reg = readl_relaxed(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
|
|
(tmdev->tsens_addr) +
|
|
(tm_sensor->sensor_num * TSENS_SN_ADDR_OFFSET));
|
|
switch (trip) {
|
|
case TSENS_TRIP_WARM:
|
|
reg = (reg & TSENS_UPPER_THRESHOLD_MASK) >>
|
|
TSENS_UPPER_THRESHOLD_SHIFT;
|
|
break;
|
|
case TSENS_TRIP_COOL:
|
|
reg = (reg & TSENS_LOWER_THRESHOLD_MASK);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
*temp = tsens_tz_code_to_degc(reg, tm_sensor->sensor_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsens_tz_notify(struct thermal_zone_device *thermal,
|
|
int count, enum thermal_trip_type type)
|
|
{
|
|
/* TSENS driver does not shutdown the device.
|
|
All Thermal notification are sent to the
|
|
thermal daemon to take appropriate action */
|
|
pr_debug("%s debug\n", __func__);
|
|
return 1;
|
|
}
|
|
|
|
static int tsens_tz_set_trip_temp(struct thermal_zone_device *thermal,
|
|
int trip, long temp)
|
|
{
|
|
struct tsens_tm_device_sensor *tm_sensor = thermal->devdata;
|
|
unsigned int reg_cntl;
|
|
int code, hi_code, lo_code, code_err_chk;
|
|
|
|
code_err_chk = code = tsens_tz_degc_to_code(temp,
|
|
tm_sensor->sensor_num);
|
|
if (!tm_sensor || trip < 0)
|
|
return -EINVAL;
|
|
|
|
lo_code = TSENS_THRESHOLD_MIN_CODE;
|
|
hi_code = TSENS_THRESHOLD_MAX_CODE;
|
|
|
|
reg_cntl = readl_relaxed(TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
|
|
(tmdev->tsens_addr) +
|
|
(tm_sensor->sensor_num * TSENS_SN_ADDR_OFFSET));
|
|
switch (trip) {
|
|
case TSENS_TRIP_WARM:
|
|
code <<= TSENS_UPPER_THRESHOLD_SHIFT;
|
|
reg_cntl &= ~TSENS_UPPER_THRESHOLD_MASK;
|
|
if (!(reg_cntl & TSENS_LOWER_STATUS_CLR))
|
|
lo_code = (reg_cntl & TSENS_LOWER_THRESHOLD_MASK);
|
|
break;
|
|
case TSENS_TRIP_COOL:
|
|
reg_cntl &= ~TSENS_LOWER_THRESHOLD_MASK;
|
|
if (!(reg_cntl & TSENS_UPPER_STATUS_CLR))
|
|
hi_code = (reg_cntl & TSENS_UPPER_THRESHOLD_MASK)
|
|
>> TSENS_UPPER_THRESHOLD_SHIFT;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (code_err_chk < lo_code || code_err_chk > hi_code)
|
|
return -EINVAL;
|
|
|
|
writel_relaxed(reg_cntl | code, (TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
|
|
(tmdev->tsens_addr) +
|
|
(tm_sensor->sensor_num *
|
|
TSENS_SN_ADDR_OFFSET)));
|
|
mb();
|
|
return 0;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops tsens_thermal_zone_ops = {
|
|
.get_temp = tsens_tz_get_temp,
|
|
.get_mode = tsens_tz_get_mode,
|
|
.get_trip_type = tsens_tz_get_trip_type,
|
|
.activate_trip_type = tsens_tz_activate_trip_type,
|
|
.get_trip_temp = tsens_tz_get_trip_temp,
|
|
.set_trip_temp = tsens_tz_set_trip_temp,
|
|
.notify = tsens_tz_notify,
|
|
};
|
|
|
|
static void notify_uspace_tsens_fn(struct work_struct *work)
|
|
{
|
|
struct tsens_tm_device_sensor *tm = container_of(work,
|
|
struct tsens_tm_device_sensor, work);
|
|
|
|
sysfs_notify(&tm->tz_dev->device.kobj,
|
|
NULL, "type");
|
|
}
|
|
|
|
static irqreturn_t tsens_isr(int irq, void *data)
|
|
{
|
|
struct tsens_tm_device *tm = data;
|
|
unsigned int i, status, threshold;
|
|
unsigned int sensor_status_addr, sensor_status_ctrl_addr;
|
|
|
|
sensor_status_addr =
|
|
(unsigned int)TSENS_S0_STATUS_ADDR(tmdev->tsens_addr);
|
|
sensor_status_ctrl_addr =
|
|
(unsigned int)TSENS_S0_UPPER_LOWER_STATUS_CTRL_ADDR
|
|
(tmdev->tsens_addr);
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++) {
|
|
bool upper_thr = false, lower_thr = false;
|
|
status = readl_relaxed(sensor_status_addr);
|
|
threshold = readl_relaxed(sensor_status_ctrl_addr);
|
|
if (status & TSENS_SN_STATUS_UPPER_STATUS) {
|
|
writel_relaxed(threshold | TSENS_UPPER_STATUS_CLR,
|
|
sensor_status_ctrl_addr);
|
|
upper_thr = true;
|
|
}
|
|
if (status & TSENS_SN_STATUS_LOWER_STATUS) {
|
|
writel_relaxed(threshold | TSENS_LOWER_STATUS_CLR,
|
|
sensor_status_ctrl_addr);
|
|
lower_thr = true;
|
|
}
|
|
if (upper_thr || lower_thr) {
|
|
/* Notify user space */
|
|
schedule_work(&tm->sensor[i].work);
|
|
pr_debug("sensor:%d trigger temp (%d degC)\n", i,
|
|
tsens_tz_code_to_degc((status &
|
|
TSENS_SN_STATUS_TEMP_MASK), i));
|
|
}
|
|
sensor_status_addr += TSENS_SN_ADDR_OFFSET;
|
|
sensor_status_ctrl_addr += TSENS_SN_ADDR_OFFSET;
|
|
}
|
|
mb();
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void tsens_hw_init(void)
|
|
{
|
|
unsigned int reg_cntl = 0;
|
|
unsigned int i;
|
|
|
|
reg_cntl = readl_relaxed(TSENS_CTRL_ADDR(tmdev->tsens_addr));
|
|
writel_relaxed(reg_cntl | TSENS_SW_RST,
|
|
TSENS_CTRL_ADDR(tmdev->tsens_addr));
|
|
writel_relaxed(TSENS_CTRL_INIT_DATA1,
|
|
TSENS_CTRL_ADDR(tmdev->tsens_addr));
|
|
writel_relaxed(TSENS_GLOBAL_INIT_DATA,
|
|
TSENS_GLOBAL_CONFIG(tmdev->tsens_addr));
|
|
writel_relaxed(TSENS_S0_MAIN_CFG_INIT_DATA,
|
|
TSENS_S0_MAIN_CONFIG(tmdev->tsens_addr));
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++) {
|
|
writel_relaxed(TSENS_SN_MIN_MAX_STATUS_CTRL_DATA,
|
|
TSENS_SN_MIN_MAX_STATUS_CTRL(tmdev->tsens_addr)
|
|
+ (i * TSENS_SN_ADDR_OFFSET));
|
|
writel_relaxed(TSENS_SN_REMOTE_CFG_DATA,
|
|
TSENS_SN_REMOTE_CONFIG(tmdev->tsens_addr)
|
|
+ (i * TSENS_SN_ADDR_OFFSET));
|
|
}
|
|
writel_relaxed(TSENS_INTERRUPT_EN,
|
|
TSENS_UPPER_LOWER_INTERRUPT_CTRL(tmdev->tsens_addr));
|
|
}
|
|
|
|
static int tsens_calib_sensors(void)
|
|
{
|
|
int i, tsens_base1_data = 0, tsens0_point1 = 0, tsens1_point1 = 0;
|
|
int tsens2_point1 = 0, tsens3_point1 = 0, tsens4_point1 = 0;
|
|
int tsens5_point1 = 0, tsens6_point1 = 0, tsens7_point1 = 0;
|
|
int tsens8_point1 = 0, tsens9_point1 = 0, tsens10_point1 = 0;
|
|
int tsens0_point2 = 0, tsens1_point2 = 0, tsens2_point2 = 0;
|
|
int tsens3_point2 = 0, tsens4_point2 = 0, tsens5_point2 = 0;
|
|
int tsens6_point2 = 0, tsens7_point2 = 0, tsens8_point2 = 0;
|
|
int tsens9_point2 = 0, tsens10_point2 = 0;
|
|
int tsens_base2_data = 0, tsens_calibration_mode = 0, temp;
|
|
uint32_t calib_data[5];
|
|
|
|
for (i = 0; i < 5; i++)
|
|
calib_data[i] = readl_relaxed(tmdev->tsens_calib_addr
|
|
+ (i * TSENS_SN_ADDR_OFFSET));
|
|
|
|
tsens_calibration_mode = (calib_data[1] & TSENS_CAL_SEL_0_1
|
|
>> TSENS_CAL_SEL_SHIFT);
|
|
temp = (calib_data[3] & TSENS_CAL_SEL_2
|
|
>> TSENS_CAL_SEL_SHIFT_2);
|
|
tsens_calibration_mode |= temp;
|
|
|
|
if (!tsens_calibration_mode) {
|
|
pr_debug("TSENS is calibrationless mode\n");
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++) {
|
|
tmdev->sensor[i].calib_data_point2 = 78000;
|
|
tmdev->sensor[i].calib_data_point1 = 49200;
|
|
goto compute_intercept_slope;
|
|
}
|
|
} else if (tsens_calibration_mode == TSENS_ONE_POINT_CALIB ||
|
|
TSENS_TWO_POINT_CALIB) {
|
|
tsens_base1_data = calib_data[0] & TSENS_BASE1_MASK;
|
|
tsens0_point1 = calib_data[0] & TSENS0_POINT1_MASK;
|
|
tsens1_point1 = calib_data[0] & TSENS1_POINT1_MASK;
|
|
tsens2_point1 = calib_data[0] & TSENS2_POINT1_MASK;
|
|
tsens3_point1 = calib_data[0] & TSENS3_POINT1_MASK;
|
|
tsens4_point1 = calib_data[1] & TSENS4_POINT1_MASK;
|
|
tsens5_point1 = calib_data[1] & TSENS5_POINT1_MASK;
|
|
tsens6_point1 = calib_data[1] & TSENS6_POINT1_MASK;
|
|
tsens7_point1 = calib_data[1] & TSENS7_POINT1_MASK;
|
|
tsens8_point1 = calib_data[1] & TSENS8_POINT1_MASK;
|
|
tsens9_point1 = calib_data[2] & TSENS9_POINT1_MASK;
|
|
tsens10_point1 = calib_data[2] & TSENS10_POINT1_MASK;
|
|
} else if (tsens_calibration_mode == TSENS_TWO_POINT_CALIB) {
|
|
tsens_base2_data = calib_data[2] & TSENS_BASE2_MASK;
|
|
tsens0_point2 = calib_data[2] & TSENS0_POINT2_MASK;
|
|
tsens1_point2 = calib_data[2] & TSENS1_POINT2_MASK;
|
|
tsens2_point2 = calib_data[3] & TSENS2_POINT2_MASK;
|
|
tsens3_point2 = calib_data[3] & TSENS3_POINT2_MASK;
|
|
tsens4_point2 = calib_data[3] & TSENS4_POINT2_MASK;
|
|
tsens5_point2 = calib_data[3] & TSENS5_POINT2_MASK;
|
|
tsens6_point2 = calib_data[3] & TSENS6_POINT2_MASK;
|
|
tsens7_point2 = calib_data[4] & TSENS7_POINT2_MASK;
|
|
tsens8_point2 = calib_data[4] & TSENS8_POINT2_MASK;
|
|
tsens9_point2 = calib_data[4] & TSENS9_POINT2_MASK;
|
|
tsens10_point2 = calib_data[4] & TSENS10_POINT2_MASK;
|
|
} else {
|
|
pr_debug("Calibration mode is unknown: %d\n",
|
|
tsens_calibration_mode);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (tsens_calibration_mode == TSENS_ONE_POINT_CALIB) {
|
|
tmdev->sensor[0].calib_data_point1 =
|
|
(((tsens_base1_data + tsens0_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[1].calib_data_point1 =
|
|
(((tsens_base1_data + tsens1_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[2].calib_data_point1 =
|
|
(((tsens_base1_data + tsens2_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[3].calib_data_point1 =
|
|
(((tsens_base1_data + tsens3_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[4].calib_data_point1 =
|
|
(((tsens_base1_data + tsens4_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[5].calib_data_point1 =
|
|
(((tsens_base1_data + tsens5_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[6].calib_data_point1 =
|
|
(((tsens_base1_data + tsens6_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[7].calib_data_point1 =
|
|
(((tsens_base1_data + tsens7_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[8].calib_data_point1 =
|
|
(((tsens_base1_data + tsens8_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[9].calib_data_point1 =
|
|
(((tsens_base1_data + tsens9_point1) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[10].calib_data_point1 =
|
|
(((tsens_base1_data + tsens10_point1) << 2) | TSENS_BIT_APPEND);
|
|
}
|
|
|
|
if (tsens_calibration_mode == TSENS_TWO_POINT_CALIB) {
|
|
tmdev->sensor[0].calib_data_point2 =
|
|
(((tsens_base2_data + tsens0_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[1].calib_data_point2 =
|
|
(((tsens_base2_data + tsens1_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[2].calib_data_point2 =
|
|
(((tsens_base2_data + tsens2_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[3].calib_data_point2 =
|
|
(((tsens_base2_data + tsens3_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[4].calib_data_point2 =
|
|
(((tsens_base2_data + tsens4_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[5].calib_data_point2 =
|
|
(((tsens_base2_data + tsens5_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[6].calib_data_point2 =
|
|
(((tsens_base2_data + tsens6_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[7].calib_data_point2 =
|
|
(((tsens_base2_data + tsens7_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[8].calib_data_point2 =
|
|
(((tsens_base2_data + tsens8_point2) << 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[9].calib_data_point2 =
|
|
(((tsens_base2_data + tsens9_point2) < 2) | TSENS_BIT_APPEND);
|
|
tmdev->sensor[10].calib_data_point2 =
|
|
(((tsens_base2_data + tsens10_point2) << 2) | TSENS_BIT_APPEND);
|
|
}
|
|
|
|
compute_intercept_slope:
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++) {
|
|
int32_t num = 0, den = 0;
|
|
if (tsens_calibration_mode == TSENS_TWO_POINT_CALIB) {
|
|
num = TSENS_CAL_DEGC_POINT2 - TSENS_CAL_DEGC_POINT2;
|
|
den = tmdev->sensor[i].calib_data_point2 -
|
|
tmdev->sensor[i].calib_data_point1;
|
|
num *= tmdev->tsens_factor;
|
|
tmdev->sensor[i].slope_mul_tsens_factor = num/den;
|
|
}
|
|
tmdev->sensor[i].offset = (TSENS_CAL_DEGC_POINT1 *
|
|
tmdev->tsens_factor)
|
|
- (tmdev->sensor[i].calib_data_point1 *
|
|
tmdev->sensor[i].slope_mul_tsens_factor);
|
|
INIT_WORK(&tmdev->sensor[i].work, notify_uspace_tsens_fn);
|
|
tmdev->prev_reading_avail = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_device_tree_data(struct platform_device *pdev)
|
|
{
|
|
const struct device_node *of_node = pdev->dev.of_node;
|
|
struct resource *res_mem = NULL;
|
|
u32 *tsens_slope_data;
|
|
u32 rc = 0, i, tsens_num_sensors;
|
|
|
|
rc = of_property_read_u32(of_node,
|
|
"qcom,sensors", &tsens_num_sensors);
|
|
if (rc) {
|
|
dev_err(&pdev->dev, "missing sensor number\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
tsens_slope_data = devm_kzalloc(&pdev->dev,
|
|
tsens_num_sensors, GFP_KERNEL);
|
|
if (!tsens_slope_data) {
|
|
dev_err(&pdev->dev, "can not allocate slope data\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
rc = of_property_read_u32_array(of_node,
|
|
"qcom,slope", tsens_slope_data, tsens_num_sensors);
|
|
if (rc) {
|
|
dev_err(&pdev->dev, "invalid or missing property: tsens-slope\n");
|
|
return rc;
|
|
};
|
|
|
|
tmdev = devm_kzalloc(&pdev->dev,
|
|
sizeof(struct tsens_tm_device) +
|
|
tsens_num_sensors *
|
|
sizeof(struct tsens_tm_device_sensor),
|
|
GFP_KERNEL);
|
|
if (tmdev == NULL) {
|
|
pr_err("%s: kzalloc() failed.\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
for (i = 0; i < tsens_num_sensors; i++)
|
|
tmdev->sensor[i].slope_mul_tsens_factor = tsens_slope_data[i];
|
|
tmdev->tsens_factor = TSENS_SLOPE_FACTOR;
|
|
tmdev->tsens_num_sensor = tsens_num_sensors;
|
|
|
|
tmdev->tsens_irq = platform_get_irq(pdev, 0);
|
|
if (tmdev->tsens_irq < 0) {
|
|
pr_err("Invalid get irq\n");
|
|
return tmdev->tsens_irq;
|
|
}
|
|
|
|
tmdev->res_tsens_mem = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, "tsens_physical");
|
|
if (!tmdev->res_tsens_mem) {
|
|
pr_err("Could not get tsens physical address resource\n");
|
|
rc = -EINVAL;
|
|
goto fail_free_irq;
|
|
}
|
|
|
|
tmdev->tsens_len = tmdev->res_tsens_mem->end -
|
|
tmdev->res_tsens_mem->start + 1;
|
|
|
|
res_mem = request_mem_region(tmdev->res_tsens_mem->start,
|
|
tmdev->tsens_len, tmdev->res_tsens_mem->name);
|
|
if (!res_mem) {
|
|
pr_err("Request tsens physical memory region failed\n");
|
|
rc = -EINVAL;
|
|
goto fail_free_irq;
|
|
}
|
|
|
|
tmdev->tsens_addr = ioremap(res_mem->start, tmdev->tsens_len);
|
|
if (!tmdev->tsens_addr) {
|
|
pr_err("Failed to IO map TSENS registers.\n");
|
|
rc = -EINVAL;
|
|
goto fail_unmap_tsens_region;
|
|
}
|
|
|
|
tmdev->res_calib_mem = platform_get_resource_byname(pdev,
|
|
IORESOURCE_MEM, "tsens_eeprom_physical");
|
|
if (!tmdev->res_calib_mem) {
|
|
pr_err("Could not get qfprom physical address resource\n");
|
|
rc = -EINVAL;
|
|
goto fail_unmap_tsens;
|
|
}
|
|
|
|
tmdev->calib_len = tmdev->res_calib_mem->end -
|
|
tmdev->res_calib_mem->start + 1;
|
|
|
|
res_mem = request_mem_region(tmdev->res_calib_mem->start,
|
|
tmdev->calib_len, tmdev->res_calib_mem->name);
|
|
if (!res_mem) {
|
|
pr_err("Request calibration memory region failed\n");
|
|
rc = -EINVAL;
|
|
goto fail_unmap_tsens;
|
|
}
|
|
|
|
tmdev->tsens_calib_addr = ioremap(res_mem->start,
|
|
tmdev->calib_len);
|
|
if (!tmdev->tsens_calib_addr) {
|
|
pr_err("Failed to IO map EEPROM registers.\n");
|
|
rc = -EINVAL;
|
|
goto fail_unmap_calib_region;
|
|
}
|
|
|
|
return 0;
|
|
|
|
fail_unmap_calib_region:
|
|
if (tmdev->res_calib_mem)
|
|
release_mem_region(tmdev->res_calib_mem->start,
|
|
tmdev->calib_len);
|
|
fail_unmap_tsens:
|
|
if (tmdev->tsens_addr)
|
|
iounmap(tmdev->tsens_addr);
|
|
fail_unmap_tsens_region:
|
|
if (tmdev->res_tsens_mem)
|
|
release_mem_region(tmdev->res_tsens_mem->start,
|
|
tmdev->tsens_len);
|
|
fail_free_irq:
|
|
free_irq(tmdev->tsens_irq, tmdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __devinit tsens_tm_probe(struct platform_device *pdev)
|
|
{
|
|
int rc;
|
|
|
|
if (tmdev) {
|
|
pr_err("TSENS device already in use\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (pdev->dev.of_node)
|
|
rc = get_device_tree_data(pdev);
|
|
else
|
|
return -ENODEV;
|
|
|
|
tmdev->pdev = pdev;
|
|
rc = tsens_calib_sensors();
|
|
if (rc < 0)
|
|
goto fail;
|
|
|
|
tsens_hw_init();
|
|
tmdev->prev_reading_avail = true;
|
|
|
|
platform_set_drvdata(pdev, tmdev);
|
|
|
|
return 0;
|
|
fail:
|
|
if (tmdev->tsens_calib_addr)
|
|
iounmap(tmdev->tsens_calib_addr);
|
|
if (tmdev->res_calib_mem)
|
|
release_mem_region(tmdev->res_calib_mem->start,
|
|
tmdev->calib_len);
|
|
if (tmdev->tsens_addr)
|
|
iounmap(tmdev->tsens_addr);
|
|
if (tmdev->res_tsens_mem)
|
|
release_mem_region(tmdev->res_tsens_mem->start,
|
|
tmdev->tsens_len);
|
|
free_irq(tmdev->tsens_irq, tmdev);
|
|
kfree(tmdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __devinit _tsens_register_thermal(void)
|
|
{
|
|
struct platform_device *pdev;
|
|
int rc, i;
|
|
|
|
if (!tmdev) {
|
|
pr_err("%s: TSENS early init not done\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
pdev = tmdev->pdev;
|
|
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++) {
|
|
char name[18];
|
|
snprintf(name, sizeof(name), "tsens_tz_sensor%d", i);
|
|
tmdev->sensor[i].mode = THERMAL_DEVICE_ENABLED;
|
|
tmdev->sensor[i].sensor_num = i;
|
|
tmdev->sensor[i].tz_dev = thermal_zone_device_register(name,
|
|
TSENS_TRIP_NUM, &tmdev->sensor[i],
|
|
&tsens_thermal_zone_ops, 0, 0, 0, 0);
|
|
if (IS_ERR(tmdev->sensor[i].tz_dev)) {
|
|
pr_err("%s: thermal_zone_device_register() failed.\n",
|
|
__func__);
|
|
rc = -ENODEV;
|
|
goto fail;
|
|
}
|
|
}
|
|
rc = request_irq(tmdev->tsens_irq, tsens_isr,
|
|
IRQF_TRIGGER_RISING, "tsens_interrupt", tmdev);
|
|
if (rc < 0) {
|
|
pr_err("%s: request_irq FAIL: %d\n", __func__, rc);
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++)
|
|
thermal_zone_device_unregister(tmdev->sensor[i].tz_dev);
|
|
goto fail;
|
|
}
|
|
platform_set_drvdata(pdev, tmdev);
|
|
|
|
return 0;
|
|
fail:
|
|
if (tmdev->tsens_calib_addr)
|
|
iounmap(tmdev->tsens_calib_addr);
|
|
if (tmdev->res_calib_mem)
|
|
release_mem_region(tmdev->res_calib_mem->start,
|
|
tmdev->calib_len);
|
|
if (tmdev->tsens_addr)
|
|
iounmap(tmdev->tsens_addr);
|
|
if (tmdev->res_tsens_mem)
|
|
release_mem_region(tmdev->res_tsens_mem->start,
|
|
tmdev->tsens_len);
|
|
kfree(tmdev);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int __devexit tsens_tm_remove(struct platform_device *pdev)
|
|
{
|
|
struct tsens_tm_device *tmdev = platform_get_drvdata(pdev);
|
|
int i;
|
|
|
|
for (i = 0; i < tmdev->tsens_num_sensor; i++)
|
|
thermal_zone_device_unregister(tmdev->sensor[i].tz_dev);
|
|
if (tmdev->tsens_calib_addr)
|
|
iounmap(tmdev->tsens_calib_addr);
|
|
if (tmdev->res_calib_mem)
|
|
release_mem_region(tmdev->res_calib_mem->start,
|
|
tmdev->calib_len);
|
|
if (tmdev->tsens_addr)
|
|
iounmap(tmdev->tsens_addr);
|
|
if (tmdev->res_tsens_mem)
|
|
release_mem_region(tmdev->res_tsens_mem->start,
|
|
tmdev->tsens_len);
|
|
free_irq(tmdev->tsens_irq, tmdev);
|
|
platform_set_drvdata(pdev, NULL);
|
|
kfree(tmdev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct of_device_id tsens_match[] = {
|
|
{ .compatible = "qcom,msm-tsens",
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver tsens_tm_driver = {
|
|
.probe = tsens_tm_probe,
|
|
.remove = tsens_tm_remove,
|
|
.driver = {
|
|
.name = "msm-tsens",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = tsens_match,
|
|
},
|
|
};
|
|
|
|
static int __init tsens_tm_init_driver(void)
|
|
{
|
|
return platform_driver_register(&tsens_tm_driver);
|
|
}
|
|
arch_initcall(tsens_tm_init_driver);
|
|
|
|
static int __init tsens_thermal_register(void)
|
|
{
|
|
return _tsens_register_thermal();
|
|
}
|
|
module_init(tsens_thermal_register);
|
|
|
|
static void __exit _tsens_tm_remove(void)
|
|
{
|
|
platform_driver_unregister(&tsens_tm_driver);
|
|
}
|
|
module_exit(_tsens_tm_remove);
|
|
|
|
MODULE_ALIAS("platform:" TSENS_DRIVER_NAME);
|
|
MODULE_LICENSE("GPL v2");
|