From 3302e1e1a3cfa4e67fda2a61d6f0c42205d40932 Mon Sep 17 00:00:00 2001 From: Rajith Cherian Date: Tue, 14 Feb 2017 18:30:43 +0530 Subject: [PATCH] ipq8064: tsens: Base tsens driver for IPQ8064 Add TSENS driver template to support IPQ8064. This is a base file copied from tsens-8960.c Change-Id: I47c573fdfa2d898243c6a6ba952d1632f91391f7 Signed-off-by: Rajith Cherian ipq8064: tsens: TSENS driver support for IPQ8064 Support for IPQ8064 tsens driver. The driver works with the thermal framework. The driver overrides the following fucntionalities: 1. Get current temperature. 2. Get/Set trip temperatures. 3. Enabled/Disable trip points. 4. ISR for threshold generated interrupt. 5. Notify userspace when trip points are hit. Change-Id: I8bc7204fd627d10875ab13fc1de8cb6c2ed7a918 Signed-off-by: Rajith Cherian --- --- a/drivers/thermal/qcom/Makefile +++ b/drivers/thermal/qcom/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_QCOM_TSENS) += qcom_tsens.o -qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-v2.o +qcom_tsens-y += tsens.o tsens-common.o tsens-8916.o tsens-8974.o tsens-8960.o tsens-v2.o \ + tsens-ipq8064.o --- /dev/null +++ b/drivers/thermal/qcom/tsens-ipq8064.c @@ -0,0 +1,551 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "tsens.h" + +#define CAL_MDEGC 30000 + +#define CONFIG_ADDR 0x3640 +/* CONFIG_ADDR bitmasks */ +#define CONFIG 0x9b +#define CONFIG_MASK 0xf +#define CONFIG_SHIFT 0 + +#define STATUS_CNTL_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 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_CODE 0x20000 +#define THRESHOLD_MIN_CODE 0 +#define THRESHOLD_MAX_LIMIT_SHIFT 24 +#define THRESHOLD_MIN_LIMIT_SHIFT 16 +#define THRESHOLD_UPPER_LIMIT_SHIFT 8 +#define THRESHOLD_LOWER_LIMIT_SHIFT 0 +#define THRESHOLD_MAX_LIMIT_MASK (THRESHOLD_MAX_CODE << \ + THRESHOLD_MAX_LIMIT_SHIFT) +#define THRESHOLD_MIN_LIMIT_MASK (THRESHOLD_MAX_CODE << \ + THRESHOLD_MIN_LIMIT_SHIFT) +#define THRESHOLD_UPPER_LIMIT_MASK (THRESHOLD_MAX_CODE << \ + THRESHOLD_UPPER_LIMIT_SHIFT) +#define THRESHOLD_LOWER_LIMIT_MASK (THRESHOLD_MAX_CODE << \ + THRESHOLD_LOWER_LIMIT_SHIFT) + +/* Initial temperature threshold values */ +#define LOWER_LIMIT_TH 0x9d /* 95C */ +#define UPPER_LIMIT_TH 0xa6 /* 105C */ +#define MIN_LIMIT_TH 0x0 +#define MAX_LIMIT_TH 0xff + +#define S0_STATUS_ADDR 0x3628 +#define STATUS_ADDR_OFFSET 2 +#define SENSOR_STATUS_SIZE 4 +#define INT_STATUS_ADDR 0x363c +#define TRDY_MASK BIT(7) +#define TIMEOUT_US 100 + +#define TSENS_EN BIT(0) +#define TSENS_SW_RST BIT(1) +#define TSENS_ADC_CLK_SEL BIT(2) +#define SENSOR0_EN BIT(3) +#define SENSOR1_EN BIT(4) +#define SENSOR2_EN BIT(5) +#define SENSOR3_EN BIT(6) +#define SENSOR4_EN BIT(7) +#define SENSORS_EN (SENSOR0_EN | SENSOR1_EN | \ + SENSOR2_EN | SENSOR3_EN | SENSOR4_EN) +#define TSENS_8064_SENSOR5_EN BIT(8) +#define TSENS_8064_SENSOR6_EN BIT(9) +#define TSENS_8064_SENSOR7_EN BIT(10) +#define TSENS_8064_SENSOR8_EN BIT(11) +#define TSENS_8064_SENSOR9_EN BIT(12) +#define TSENS_8064_SENSOR10_EN BIT(13) +#define TSENS_8064_SENSORS_EN (SENSORS_EN | \ + TSENS_8064_SENSOR5_EN | \ + TSENS_8064_SENSOR6_EN | \ + TSENS_8064_SENSOR7_EN | \ + TSENS_8064_SENSOR8_EN | \ + TSENS_8064_SENSOR9_EN | \ + TSENS_8064_SENSOR10_EN) + +#define TSENS_8064_SEQ_SENSORS 5 +#define TSENS_8064_S4_S5_OFFSET 40 +#define TSENS_FACTOR 1 + +/* Trips: from very hot to very cold */ +enum tsens_trip_type { + TSENS_TRIP_STAGE3 = 0, + TSENS_TRIP_STAGE2, + TSENS_TRIP_STAGE1, + TSENS_TRIP_STAGE0, + TSENS_TRIP_NUM, +}; + +u32 tsens_8064_slope[] = { + 1176, 1176, 1154, 1176, + 1111, 1132, 1132, 1199, + 1132, 1199, 1132 + }; + +/* Temperature on y axis and ADC-code on x-axis */ +static inline int code_to_degC(u32 adc_code, const struct tsens_sensor *s) +{ + int degcbeforefactor, degc; + + degcbeforefactor = (adc_code * s->slope) + s->offset; + + if (degcbeforefactor == 0) + degc = degcbeforefactor; + else if (degcbeforefactor > 0) + degc = (degcbeforefactor + TSENS_FACTOR/2) + / TSENS_FACTOR; + else + degc = (degcbeforefactor - TSENS_FACTOR/2) + / TSENS_FACTOR; + + return degc; +} + +static int degC_to_code(int degC, const struct tsens_sensor *s) +{ + int code = ((degC * TSENS_FACTOR - s->offset) + (s->slope/2)) + / s->slope; + + if (code > THRESHOLD_MAX_CODE) + code = THRESHOLD_MAX_CODE; + else if (code < THRESHOLD_MIN_CODE) + code = THRESHOLD_MIN_CODE; + return code; +} + +static int suspend_ipq8064(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; + + mask = SLP_CLK_ENA | EN; + + ret = regmap_update_bits(map, CNTL_ADDR, mask, 0); + if (ret) + return ret; + + return 0; +} + +static int resume_ipq8064(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; + + 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 void notify_uspace_tsens_fn(struct work_struct *work) +{ + struct tsens_sensor *s = container_of(work, struct tsens_sensor, + notify_work); + + sysfs_notify(&s->tzd->device.kobj, NULL, "type"); +} + +static void tsens_scheduler_fn(struct work_struct *work) +{ + struct tsens_device *tmdev = container_of(work, struct tsens_device, + tsens_work); + unsigned int threshold, threshold_low, code, reg, sensor, mask; + unsigned int sensor_addr; + bool upper_th_x, lower_th_x; + int adc_code, ret; + + ret = regmap_read(tmdev->map, STATUS_CNTL_8064, ®); + if (ret) + return; + reg = reg | LOWER_STATUS_CLR | UPPER_STATUS_CLR; + ret = regmap_write(tmdev->map, STATUS_CNTL_8064, reg); + if (ret) + return; + + mask = ~(LOWER_STATUS_CLR | UPPER_STATUS_CLR); + ret = regmap_read(tmdev->map, THRESHOLD_ADDR, &threshold); + if (ret) + return; + threshold_low = (threshold & THRESHOLD_LOWER_LIMIT_MASK) + >> THRESHOLD_LOWER_LIMIT_SHIFT; + threshold = (threshold & THRESHOLD_UPPER_LIMIT_MASK) + >> THRESHOLD_UPPER_LIMIT_SHIFT; + + ret = regmap_read(tmdev->map, STATUS_CNTL_8064, ®); + if (ret) + return; + + ret = regmap_read(tmdev->map, CNTL_ADDR, &sensor); + if (ret) + return; + sensor &= (uint32_t) TSENS_8064_SENSORS_EN; + sensor >>= SENSOR0_SHIFT; + + /* Constraint: There is only 1 interrupt control register for all + * 11 temperature sensor. So monitoring more than 1 sensor based + * on interrupts will yield inconsistent result. To overcome this + * issue we will monitor only sensor 0 which is the master sensor. + */ + + /* Skip if the sensor is disabled */ + if (sensor & 1) { + ret = regmap_read(tmdev->map, tmdev->sensor[0].status, &code); + if (ret) + return; + upper_th_x = code >= threshold; + lower_th_x = code <= threshold_low; + if (upper_th_x) + mask |= UPPER_STATUS_CLR; + if (lower_th_x) + mask |= LOWER_STATUS_CLR; + if (upper_th_x || lower_th_x) { + /* Notify user space */ + schedule_work(&tmdev->sensor[0].notify_work); + regmap_read(tmdev->map, sensor_addr, &adc_code); + pr_debug("Trigger (%d degrees) for sensor %d\n", + code_to_degC(adc_code, &tmdev->sensor[0]), 0); + } + } + regmap_write(tmdev->map, STATUS_CNTL_8064, reg & mask); + + /* force memory to sync */ + mb(); +} + +static irqreturn_t tsens_isr(int irq, void *data) +{ + struct tsens_device *tmdev = data; + + schedule_work(&tmdev->tsens_work); + return IRQ_HANDLED; +} + +static void hw_init(struct tsens_device *tmdev) +{ + int ret; + unsigned int reg_cntl = 0, reg_cfg = 0, reg_thr = 0; + unsigned int reg_status_cntl = 0; + + regmap_read(tmdev->map, CNTL_ADDR, ®_cntl); + regmap_write(tmdev->map, CNTL_ADDR, reg_cntl | TSENS_SW_RST); + + reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18) + | (((1 << tmdev->num_sensors) - 1) << SENSOR0_SHIFT); + regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); + regmap_read(tmdev->map, STATUS_CNTL_8064, ®_status_cntl); + reg_status_cntl |= LOWER_STATUS_CLR | UPPER_STATUS_CLR + | MIN_STATUS_MASK | MAX_STATUS_MASK; + regmap_write(tmdev->map, STATUS_CNTL_8064, reg_status_cntl); + reg_cntl |= TSENS_EN; + regmap_write(tmdev->map, CNTL_ADDR, reg_cntl); + + regmap_read(tmdev->map, CONFIG_ADDR, ®_cfg); + reg_cfg = (reg_cfg & ~CONFIG_MASK) | (CONFIG << CONFIG_SHIFT); + regmap_write(tmdev->map, CONFIG_ADDR, reg_cfg); + + reg_thr |= (LOWER_LIMIT_TH << THRESHOLD_LOWER_LIMIT_SHIFT) + | (UPPER_LIMIT_TH << THRESHOLD_UPPER_LIMIT_SHIFT) + | (MIN_LIMIT_TH << THRESHOLD_MIN_LIMIT_SHIFT) + | (MAX_LIMIT_TH << THRESHOLD_MAX_LIMIT_SHIFT); + + regmap_write(tmdev->map, THRESHOLD_ADDR, reg_thr); + + ret = devm_request_irq(tmdev->dev, tmdev->tsens_irq, tsens_isr, + IRQF_TRIGGER_RISING, "tsens_interrupt", tmdev); + if (ret < 0) { + pr_err("%s: request_irq FAIL: %d\n", __func__, ret); + return; + } + + INIT_WORK(&tmdev->tsens_work, tsens_scheduler_fn); +} + +static int init_ipq8064(struct tsens_device *tmdev) +{ + int ret, i; + u32 reg_cntl, offset = 0; + + init_common(tmdev); + 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 >= TSENS_8064_SEQ_SENSORS) + offset = TSENS_8064_S4_S5_OFFSET; + + tmdev->sensor[i].status = S0_STATUS_ADDR + offset + + (i << STATUS_ADDR_OFFSET); + tmdev->sensor[i].slope = tsens_8064_slope[i]; + INIT_WORK(&tmdev->sensor[i].notify_work, + notify_uspace_tsens_fn); + } + + reg_cntl = SW_RST; + ret = regmap_update_bits(tmdev->map, CNTL_ADDR, SW_RST, reg_cntl); + if (ret) + return ret; + + reg_cntl |= SLP_CLK_ENA | (MEASURE_PERIOD << 18); + reg_cntl &= ~SW_RST; + ret = regmap_update_bits(tmdev->map, CONFIG_ADDR, + CONFIG_MASK, CONFIG); + + 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_ipq8064(struct tsens_device *tmdev) +{ + int i; + char *data, *data_backup; + + ssize_t num_read = tmdev->num_sensors; + struct tsens_sensor *s = tmdev->sensor; + + data = qfprom_read(tmdev->dev, "calib"); + if (IS_ERR(data)) { + pr_err("Calibration not found.\n"); + return PTR_ERR(data); + } + + data_backup = qfprom_read(tmdev->dev, "calib_backup"); + if (IS_ERR(data_backup)) { + pr_err("Backup calibration not found.\n"); + return PTR_ERR(data_backup); + } + + for (i = 0; i < num_read; i++) { + s[i].calib_data = readb_relaxed(data + i); + s[i].calib_data_backup = readb_relaxed(data_backup + i); + + if (s[i].calib_data_backup) + s[i].calib_data = s[i].calib_data_backup; + if (!s[i].calib_data) { + pr_err("QFPROM TSENS calibration data not present\n"); + return -ENODEV; + } + s[i].slope = tsens_8064_slope[i]; + s[i].offset = CAL_MDEGC - (s[i].calib_data * s[i].slope); + } + + hw_init(tmdev); + + return 0; +} + +static int get_temp_ipq8064(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_degC(code, s); + return 0; + } while (time_before(jiffies, timeout)); + + return -ETIMEDOUT; +} + +static int set_trip_temp_ipq8064(void *data, int trip, int temp) +{ + unsigned int reg_th, reg_cntl; + int ret, code, code_chk, hi_code, lo_code; + const struct tsens_sensor *s = data; + struct tsens_device *tmdev = s->tmdev; + + code_chk = code = degC_to_code(temp, s); + + if (code < THRESHOLD_MIN_CODE || code > THRESHOLD_MAX_CODE) + return -EINVAL; + + ret = regmap_read(tmdev->map, STATUS_CNTL_8064, ®_cntl); + if (ret) + return ret; + + ret = regmap_read(tmdev->map, THRESHOLD_ADDR, ®_th); + if (ret) + return ret; + + hi_code = (reg_th & THRESHOLD_UPPER_LIMIT_MASK) + >> THRESHOLD_UPPER_LIMIT_SHIFT; + lo_code = (reg_th & THRESHOLD_LOWER_LIMIT_MASK) + >> THRESHOLD_LOWER_LIMIT_SHIFT; + + switch (trip) { + case TSENS_TRIP_STAGE3: + code <<= THRESHOLD_MAX_LIMIT_SHIFT; + reg_th &= ~THRESHOLD_MAX_LIMIT_MASK; + break; + case TSENS_TRIP_STAGE2: + if (code_chk <= lo_code) + return -EINVAL; + code <<= THRESHOLD_UPPER_LIMIT_SHIFT; + reg_th &= ~THRESHOLD_UPPER_LIMIT_MASK; + break; + case TSENS_TRIP_STAGE1: + if (code_chk >= hi_code) + return -EINVAL; + code <<= THRESHOLD_LOWER_LIMIT_SHIFT; + reg_th &= ~THRESHOLD_LOWER_LIMIT_MASK; + break; + case TSENS_TRIP_STAGE0: + code <<= THRESHOLD_MIN_LIMIT_SHIFT; + reg_th &= ~THRESHOLD_MIN_LIMIT_MASK; + break; + default: + return -EINVAL; + } + + ret = regmap_write(tmdev->map, THRESHOLD_ADDR, reg_th | code); + if (ret) + return ret; + + return 0; +} + +static int set_trip_activate_ipq8064(void *data, int trip, + enum thermal_trip_activation_mode mode) +{ + unsigned int reg_cntl, mask, val; + const struct tsens_sensor *s = data; + struct tsens_device *tmdev = s->tmdev; + int ret; + + if (!tmdev || trip < 0) + return -EINVAL; + + ret = regmap_read(tmdev->map, STATUS_CNTL_8064, ®_cntl); + if (ret) + return ret; + + switch (trip) { + case TSENS_TRIP_STAGE3: + mask = MAX_STATUS_MASK; + break; + case TSENS_TRIP_STAGE2: + mask = UPPER_STATUS_CLR; + break; + case TSENS_TRIP_STAGE1: + mask = LOWER_STATUS_CLR; + break; + case TSENS_TRIP_STAGE0: + mask = MIN_STATUS_MASK; + break; + default: + return -EINVAL; + } + + if (mode == THERMAL_TRIP_ACTIVATION_DISABLED) + val = reg_cntl | mask; + else + val = reg_cntl & ~mask; + + ret = regmap_write(tmdev->map, STATUS_CNTL_8064, val); + if (ret) + return ret; + + /* force memory to sync */ + mb(); + return 0; +} + +const struct tsens_ops ops_ipq8064 = { + .init = init_ipq8064, + .calibrate = calibrate_ipq8064, + .get_temp = get_temp_ipq8064, + .suspend = suspend_ipq8064, + .resume = resume_ipq8064, + .set_trip_temp = set_trip_temp_ipq8064, + .set_trip_activate = set_trip_activate_ipq8064, +}; + +const struct tsens_data data_ipq8064 = { + .num_sensors = 11, + .ops = &ops_ipq8064, +}; --- a/drivers/thermal/qcom/tsens.c +++ b/drivers/thermal/qcom/tsens.c @@ -75,8 +75,11 @@ static const struct of_device_id tsens_t }, { .compatible = "qcom,tsens-v2", .data = &data_tsens_v2, + }, { + .compatible = "qcom,ipq8064-tsens", + .data = &data_ipq8064, }, - {} + {} }; MODULE_DEVICE_TABLE(of, tsens_table); --- a/drivers/thermal/qcom/tsens.h +++ b/drivers/thermal/qcom/tsens.h @@ -89,7 +89,7 @@ int init_common(struct tsens_device *); int get_temp_common(struct tsens_device *, int, int *); /* TSENS v1 targets */ -extern const struct tsens_data data_8916, data_8974, data_8960; +extern const struct tsens_data data_8916, data_8974, data_8960, data_ipq8064; /* TSENS v2 targets */ extern const struct tsens_data data_8996, data_tsens_v2;