1132 lines
30 KiB
C
1132 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Thermal Sensor driver of ArtInChip SoC
|
|
*
|
|
* Copyright (C) 2020-2024 ArtInChip Technology Co., Ltd.
|
|
* Authors: Matteo <duanmt@artinchip.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/of.h>
|
|
#include <linux/module.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/thermal.h>
|
|
#include <linux/nvmem-consumer.h>
|
|
|
|
#define AIC_TSEN_NAME "aic-tsen"
|
|
#define AIC_TSEN_MAX_CH 4
|
|
#define AIC_TSEN_TIMEOUT msecs_to_jiffies(10000)
|
|
|
|
enum aic_tsen_ch_id {
|
|
AIC_TSEN_CH_CPU = 0,
|
|
AIC_TSEN_CH_GPAI = 1,
|
|
};
|
|
|
|
struct aic_tsen_ch_dat {
|
|
char name[16];
|
|
int slope; // 10000 * actual slope
|
|
int offset; // 10000 * actual offset
|
|
};
|
|
|
|
/* tsen compile-time platform data */
|
|
struct aic_tsen_plat_data {
|
|
const u32 num;
|
|
struct aic_tsen_ch_dat ch[AIC_TSEN_MAX_CH];
|
|
};
|
|
|
|
enum aic_tsen_mode {
|
|
AIC_TSEN_MODE_SINGLE = 0,
|
|
AIC_TSEN_MODE_PERIOD = 1
|
|
};
|
|
|
|
/* Register definition of Thermal Sensor Controller */
|
|
#define TSEN_MCR 0x000
|
|
#define TSEN_INTR 0x004
|
|
#define TSENn_CFG(n) (0x100 + (((n) & 0x3) << 5))
|
|
#define TSENn_ITV(n) (0x100 + (((n) & 0x3) << 5) + 0x4)
|
|
#define TSENn_FIL(n) (0x100 + (((n) & 0x3) << 5) + 0x8)
|
|
#define TSENn_DATA(n) (0x100 + (((n) & 0x3) << 5) + 0xC)
|
|
#define TSENn_INT(n) (0x100 + (((n) & 0x3) << 5) + 0x10)
|
|
#define TSENn_LTAV(n) (0x100 + (((n) & 0x3) << 5) + 0x14)
|
|
#define TSENn_HTAV(n) (0x100 + (((n) & 0x3) << 5) + 0x18)
|
|
#define TSENn_OTPV(n) (0x100 + (((n) & 0x3) << 5) + 0x1C)
|
|
#define TSEN_VERSION 0xFFC
|
|
|
|
#define TSEN_MCR_CH0_EN BIT(16)
|
|
#define TSEN_MCR_CH_EN(n) (TSEN_MCR_CH0_EN << (n))
|
|
#define TSEN_MCR_ACQ_SHIFT 8
|
|
#define TSEN_MCR_ACQ_MASK GENMASK(15, 8)
|
|
#define TSEN_MCR_MAX_SHIFT 4
|
|
#define TSEN_MCR_MAX_MASK GENMASK(5, 4)
|
|
#define TSEN_MCR_EN BIT(0)
|
|
|
|
#define TSEN_INTR_CH_INT_FLAG(n) (BIT(16) << (n))
|
|
#define TSEN_INTR_CH_INT_EN(n) (BIT(0) << (n))
|
|
|
|
#define TSENn_CFG_ADC_ACQ_VAL 0xff
|
|
#define TSENn_CFG_ADC_ACQ_SHIFT 8
|
|
#define TSENn_CFG_ADC_ACQ_MASK GENMASK(15, 8)
|
|
#define TSENn_CFG_HIGH_ADC_PRIORITY BIT(4)
|
|
#define TSENn_CFG_PERIOD_SAMPLE_EN BIT(1)
|
|
#define TSENn_CFG_SINGLE_SAMPLE_EN BIT(0)
|
|
|
|
#define TSENn_ITV_SHIFT 16
|
|
|
|
#define TSENn_FIL_2_POINTS 1
|
|
#define TSENn_FIL_4_POINTS 2
|
|
#define TSENn_FIL_8_POINTS 3
|
|
|
|
#define TSENn_INT_OTP_FLAG BIT(28)
|
|
#define TSENn_INT_HTA_RM_FLAG BIT(27)
|
|
#define TSENn_INT_HTA_VALID_FLAG BIT(26)
|
|
#define TSENn_INT_LTA_RM_FLAG BIT(25)
|
|
#define TSENn_INT_LTA_VALID_FLAG BIT(24)
|
|
#define TSENn_INT_DAT_OVW_FLAG BIT(17)
|
|
#define TSENn_INT_DAT_RDY_FLAG BIT(16)
|
|
#define TSENn_INT_OTP_RESET (0xA << 12)
|
|
#define TSENn_INT_OTP_IE (0x5 << 12)
|
|
#define TSENn_INT_HTA_RM_IE BIT(11)
|
|
#define TSENn_INT_HTA_VALID_IE BIT(10)
|
|
#define TSENn_INT_LTA_RM_IE BIT(9)
|
|
#define TSENn_INT_LTA_VALID_IE BIT(8)
|
|
#define TSENn_INT_DAT_OVW_IE BIT(1)
|
|
#define TSENn_INT_DATA_RDY_IE BIT(0)
|
|
|
|
#define TSENn_HLTA_EN BIT(31)
|
|
#define TSENn_HLTA_RM_THD_SHIFT 16
|
|
#define TSENn_HLTA_RM_THD_MASK GENMASK(27, 16)
|
|
#define TSENn_HLTA_THD_MASK GENMASK(11, 0)
|
|
|
|
#define TSENn_OTPV_EN BIT(31)
|
|
#define TSENn_OTPV_VAL_MASK GENMASK(11, 0)
|
|
|
|
#define TSEN_NVMEM_CELL_NUM 8
|
|
|
|
#define TSEN_THS0_ADC_VAL_LOW 0
|
|
#define TSEN_THS0_ADC_VAL_LOW_MASK BIT(12)
|
|
#define TSEN_THS1_ADC_VAL_LOW 1
|
|
#define TSEN_THS1_ADC_VAL_LOW_MASK BIT(12)
|
|
#define TSEN_THS0_ADC_VAL_HIGH 2
|
|
#define TSEN_THS0_ADC_VAL_HIGH_MASK BIT(12)
|
|
#define TSEN_THS1_ADC_VAL_HIGH 3
|
|
#define TSEN_THS1_ADC_VAL_HIGH_MASK BIT(12)
|
|
#define TSEN_THS_ENV_TEMP_LOW 4
|
|
#define TSEN_THS_ENV_TEMP_LOW_MASK BIT(4)
|
|
#define TSEN_THS_ENV_TEMP_HIGH 5
|
|
#define TSEN_THS_ENV_TEMP_HIGH_MASK BIT(8)
|
|
#define TSEN_LDO30_BG_CTRL 6
|
|
#define TSEN_LDO30_BG_CTRL_MASK GENMASK(7, 0)
|
|
#define TSEN_CP_VERSION 7
|
|
#define TSEN_CP_VERSION_MASK BIT(6)
|
|
#define TSEN_ENV_TEMP_LOW_SIGN_MASK BIT(3)
|
|
#define TSEN_ENV_TEMP_HIGH_SIGN_MASK BIT(7)
|
|
#define TSEN_ENV_TEMP_LOW_BASE 25
|
|
#define TSEN_ENV_TEMP_HIGH_BASE 65
|
|
|
|
#define TSEN_VOLTAGE_SCALE_UNIT 2.14285
|
|
#define TSEN_TRIM_VOLTAGE_BOUNDARY_VAL 0x80
|
|
#define TSEN_ORIGIN_STANDARD_VOLTAGE 3000 // = 3 * 1000
|
|
|
|
#define TSEN_CP_VERSION_DIFF_TYPE 0xA
|
|
#define TSEN_SINGLE_POINT_CALI_K_CPU -1132
|
|
#define TSEN_SINGLE_POINT_CALI_K_GPAI -1159
|
|
|
|
#define TSEN_CPU_ZONE_TRIPS_NUM 1
|
|
#define TSEN_GPAI_ZONE_TRIPS_NUM 0
|
|
|
|
#define THERMAL_CORE_TEMP_AMPN_SCALE 1000
|
|
#define TSENn_HTA_RM_THD(t) ((t) - 3 * THERMAL_CORE_TEMP_AMPN_SCALE)
|
|
#define TSENn_LTA_RM_THD(t) ((t) + 3 * THERMAL_CORE_TEMP_AMPN_SCALE)
|
|
#define TSEN_CALIB_ACCURACY_SCALE 10 // = 10000 / 1000
|
|
|
|
struct aic_tsen_ch {
|
|
bool available;
|
|
enum aic_tsen_mode mode;
|
|
u32 latest_data; // 1000 * actual temperature value
|
|
u32 smp_period; // in seconds
|
|
|
|
bool hta_enable; // high temperature alarm
|
|
u32 hta_thd; // 1000 * temperature value
|
|
u32 hta_rm_thd; // 1000 * temperature value
|
|
bool lta_enable; // low temperature alarm
|
|
u32 lta_thd; // 1000 * temperature value
|
|
u32 lta_rm_thd; // 1000 * temperature value
|
|
bool otp_enable; // over temperature protection
|
|
u32 otp_thd; // 1000 * temperature value
|
|
bool htp_enable; // high temperature protection
|
|
|
|
struct completion complete;
|
|
struct thermal_zone_device *zone;
|
|
};
|
|
|
|
/* Aic Thermal Sensor Dev Structure */
|
|
struct aic_tsen_dev {
|
|
struct attribute_group attrs;
|
|
void __iomem *regs;
|
|
struct platform_device *pdev;
|
|
struct clk *clk;
|
|
struct reset_control *rst;
|
|
u32 pclk_rate;
|
|
u32 irq;
|
|
u32 ch_num;
|
|
|
|
struct aic_tsen_ch chan[AIC_TSEN_MAX_CH];
|
|
struct aic_tsen_plat_data *data;
|
|
u32 cell_data[TSEN_NVMEM_CELL_NUM];
|
|
};
|
|
|
|
static DEFINE_SPINLOCK(user_lock);
|
|
|
|
/* Temperature = ADC data * slope + offset.
|
|
* 1. Temperature accuracy adopts 1000.
|
|
* 2. Slope and Offset accuracy adopts 10000.
|
|
* Because the difference between the slope value of the CPU position and the
|
|
* gpai position lies in the fourth digit.
|
|
*/
|
|
static s32 tsen_data2temp(u32 ch, u16 data, struct aic_tsen_ch_dat *dat)
|
|
{
|
|
int temp;
|
|
|
|
if (data == 4095 || data == 0)
|
|
return 0;
|
|
|
|
temp = dat->slope * data;
|
|
temp += dat->offset;
|
|
if ((temp % TSEN_CALIB_ACCURACY_SCALE) < TSEN_CALIB_ACCURACY_SCALE / 2)
|
|
temp = temp / TSEN_CALIB_ACCURACY_SCALE;
|
|
else
|
|
temp = temp / TSEN_CALIB_ACCURACY_SCALE + 1;
|
|
pr_debug("%s() ch%d temp: %d -> %d.%03d\n", __func__, ch, data,
|
|
temp / THERMAL_CORE_TEMP_AMPN_SCALE,
|
|
temp % THERMAL_CORE_TEMP_AMPN_SCALE);
|
|
return temp;
|
|
}
|
|
|
|
/* ADC data = (Temperature - offset) / slope */
|
|
static u16 tsen_temp2data(u32 ch, s32 temp, struct aic_tsen_ch_dat *dat)
|
|
{
|
|
int data = temp * TSEN_CALIB_ACCURACY_SCALE - dat->offset;
|
|
|
|
data /= dat->slope;
|
|
pr_debug("%s() ch%d data: %d -> %d\n", __func__, ch, temp, data);
|
|
return (u16)data;
|
|
}
|
|
|
|
static u32 tsen_sec2itv(struct aic_tsen_dev *tsen, u32 sec)
|
|
{
|
|
u32 tmp = 0;
|
|
|
|
tmp = tsen->pclk_rate >> 16;
|
|
tmp *= sec;
|
|
return tmp;
|
|
}
|
|
|
|
static void tsen_enable(void __iomem *regs, int enable)
|
|
{
|
|
int mcr;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&user_lock, flags);
|
|
|
|
mcr = readl(regs + TSEN_MCR);
|
|
if (enable)
|
|
mcr |= TSEN_MCR_EN;
|
|
else
|
|
mcr &= ~TSEN_MCR_EN;
|
|
|
|
mcr |= TSENn_CFG_ADC_ACQ_MASK;
|
|
writel(mcr, regs + TSEN_MCR);
|
|
spin_unlock_irqrestore(&user_lock, flags);
|
|
}
|
|
|
|
static int tsen_ch_num_read(void __iomem *regs)
|
|
{
|
|
u32 num = 0;
|
|
|
|
num = ((readl(regs + TSEN_MCR) & TSEN_MCR_MAX_MASK)
|
|
>> TSEN_MCR_MAX_SHIFT) + 1;
|
|
if (num > AIC_TSEN_MAX_CH) {
|
|
pr_warn("The max channel num %d is too big!\n", num);
|
|
num = AIC_TSEN_MAX_CH;
|
|
}
|
|
return num;
|
|
}
|
|
|
|
static void tsen_ch_enable(void __iomem *regs, u32 ch, int enable)
|
|
{
|
|
int mcr;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&user_lock, flags);
|
|
|
|
mcr = readl(regs + TSEN_MCR);
|
|
if (enable)
|
|
mcr |= TSEN_MCR_CH_EN(ch);
|
|
else
|
|
mcr &= ~TSEN_MCR_CH_EN(ch);
|
|
|
|
writel(mcr, regs + TSEN_MCR);
|
|
spin_unlock_irqrestore(&user_lock, flags);
|
|
}
|
|
|
|
static void tsen_int_enable(void __iomem *regs, u32 ch, u32 enable, u32 detail)
|
|
{
|
|
u32 val = 0;
|
|
|
|
val = readl(regs + TSEN_INTR);
|
|
if (enable) {
|
|
val |= TSEN_INTR_CH_INT_EN(ch);
|
|
writel(detail, regs + TSENn_INT(ch));
|
|
} else {
|
|
val &= ~TSEN_INTR_CH_INT_EN(ch);
|
|
writel(0, regs + TSENn_INT(ch));
|
|
}
|
|
writel(val, regs + TSEN_INTR);
|
|
}
|
|
|
|
static void tsen_single_mode(void __iomem *regs, u32 ch)
|
|
{
|
|
u32 val;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&user_lock, flags);
|
|
|
|
writel(TSENn_FIL_8_POINTS, regs + TSENn_FIL(ch));
|
|
|
|
val = readl(regs + TSENn_CFG(ch));
|
|
val &= ~TSENn_CFG_ADC_ACQ_MASK;
|
|
val |= TSENn_CFG_ADC_ACQ_VAL << TSENn_CFG_ADC_ACQ_SHIFT;
|
|
writel(val, regs + TSENn_CFG(ch));
|
|
|
|
writel(TSENn_CFG_SINGLE_SAMPLE_EN | readl(regs + TSENn_CFG(ch)),
|
|
regs + TSENn_CFG(ch));
|
|
|
|
tsen_int_enable(regs, ch, 1, TSENn_INT_DATA_RDY_IE);
|
|
spin_unlock_irqrestore(&user_lock, flags);
|
|
}
|
|
|
|
static bool tsen_support_otp(struct aic_tsen_dev *tsen)
|
|
{
|
|
static bool checked;
|
|
static bool support;
|
|
|
|
if (checked)
|
|
return support;
|
|
|
|
checked = true;
|
|
if (of_device_is_compatible(tsen->pdev->dev.of_node,
|
|
"artinchip,aic-tsen-v0.1"))
|
|
return false;
|
|
|
|
if (of_device_is_compatible(tsen->pdev->dev.of_node,
|
|
"artinchip,aic-tsen-v1.0"))
|
|
return false;
|
|
|
|
support = true;
|
|
return true;
|
|
}
|
|
|
|
/* Only in period mode, HTA, LTA, HTP and OTP are available */
|
|
static void tsen_period_mode(struct aic_tsen_dev *tsen, u32 ch)
|
|
{
|
|
u32 val, detail = TSENn_INT_DATA_RDY_IE;
|
|
void __iomem *regs = tsen->regs;
|
|
struct aic_tsen_ch *chan = &tsen->chan[ch];
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&user_lock, flags);
|
|
if (chan->hta_enable) {
|
|
detail |= TSENn_INT_HTA_RM_IE | TSENn_INT_HTA_VALID_IE;
|
|
val = TSENn_HLTA_EN
|
|
| ((chan->hta_rm_thd << TSENn_HLTA_RM_THD_SHIFT)
|
|
& TSENn_HLTA_RM_THD_MASK)
|
|
| (chan->hta_thd & TSENn_HLTA_THD_MASK);
|
|
writel(val, regs + TSENn_HTAV(ch));
|
|
}
|
|
|
|
if (chan->lta_enable) {
|
|
detail |= TSENn_INT_LTA_RM_IE | TSENn_INT_LTA_VALID_IE;
|
|
val = TSENn_HLTA_EN
|
|
| ((chan->lta_rm_thd << TSENn_HLTA_RM_THD_SHIFT)
|
|
& TSENn_HLTA_RM_THD_MASK)
|
|
| (chan->lta_thd & TSENn_HLTA_THD_MASK);
|
|
writel(val, regs + TSENn_LTAV(ch));
|
|
}
|
|
|
|
if (tsen_support_otp(tsen) && chan->otp_enable) {
|
|
detail |= TSENn_INT_OTP_RESET;
|
|
val = TSENn_OTPV_EN | (chan->otp_thd & TSENn_OTPV_VAL_MASK);
|
|
writel(val, regs + TSENn_OTPV(ch));
|
|
}
|
|
tsen_int_enable(regs, ch, 1, detail);
|
|
|
|
writel(TSENn_FIL_8_POINTS, regs + TSENn_FIL(ch));
|
|
if (of_device_is_compatible(tsen->pdev->dev.of_node,
|
|
"artinchip,aic-tsen-v0.1"))
|
|
writel(chan->smp_period << TSENn_ITV_SHIFT,
|
|
regs + TSENn_ITV(ch));
|
|
else
|
|
writel(chan->smp_period << TSENn_ITV_SHIFT | 0xFFFF,
|
|
regs + TSENn_ITV(ch));
|
|
|
|
val = readl(regs + TSENn_CFG(ch));
|
|
val &= ~TSENn_CFG_ADC_ACQ_MASK;
|
|
val |= TSENn_CFG_ADC_ACQ_VAL << TSENn_CFG_ADC_ACQ_SHIFT;
|
|
writel(val, regs + TSENn_CFG(ch));
|
|
|
|
writel(readl(regs + TSENn_CFG(ch)) | TSENn_CFG_PERIOD_SAMPLE_EN,
|
|
regs + TSENn_CFG(ch));
|
|
|
|
spin_unlock_irqrestore(&user_lock, flags);
|
|
tsen_ch_enable(regs, ch, 1);
|
|
}
|
|
|
|
static int tsen_ch_init(struct aic_tsen_dev *tsen, u32 ch)
|
|
{
|
|
struct aic_tsen_ch *chan = &tsen->chan[ch];
|
|
|
|
init_completion(&chan->complete);
|
|
if (chan->mode == AIC_TSEN_MODE_PERIOD)
|
|
tsen_period_mode(tsen, ch);
|
|
|
|
/* For single mode, should init the channel in .get_temp() */
|
|
return 0;
|
|
}
|
|
|
|
static int tsen_get_temp(struct aic_tsen_dev *tsen, u32 ch)
|
|
{
|
|
unsigned long left = 0;
|
|
struct aic_tsen_ch *chan = NULL;
|
|
struct aic_tsen_ch_dat *dat = tsen->data->ch;
|
|
unsigned long flags;
|
|
|
|
if (unlikely(ch >= tsen->ch_num)) {
|
|
dev_err(&tsen->pdev->dev, "Invalid channel num %d\n", ch);
|
|
return -ECHRNG;
|
|
}
|
|
chan = &tsen->chan[ch];
|
|
if (!chan->available) {
|
|
dev_err(&tsen->pdev->dev, "ch%d is unavailable!\n", ch);
|
|
return -ENODATA;
|
|
}
|
|
|
|
#ifndef CONFIG_ARTINCHIP_ADCIM_DM
|
|
if (chan->mode == AIC_TSEN_MODE_PERIOD)
|
|
return tsen_data2temp(ch, chan->latest_data, &dat[ch]);
|
|
#endif
|
|
|
|
spin_lock_irqsave(&user_lock, flags);
|
|
reinit_completion(&chan->complete);
|
|
spin_unlock_irqrestore(&user_lock, flags);
|
|
|
|
tsen_single_mode(tsen->regs, ch);
|
|
tsen_ch_enable(tsen->regs, ch, 1);
|
|
|
|
left = wait_for_completion_timeout(&chan->complete, AIC_TSEN_TIMEOUT);
|
|
if (!left) {
|
|
dev_err(&tsen->pdev->dev, "Ch%d read timeout!\n", ch);
|
|
tsen_ch_enable(tsen->regs, ch, 0);
|
|
return -ETIMEDOUT;
|
|
}
|
|
tsen_ch_enable(tsen->regs, ch, 0);
|
|
|
|
return tsen_data2temp(ch, chan->latest_data, &dat[ch]);
|
|
}
|
|
|
|
static inline int tsen_cpu_get_temp(struct thermal_zone_device *zone, int *temp)
|
|
{
|
|
struct aic_tsen_dev *tsen = zone->devdata;
|
|
|
|
*temp = tsen_get_temp(tsen, AIC_TSEN_CH_CPU);
|
|
return 0;
|
|
}
|
|
|
|
static inline int tsen_gpai_get_temp(struct thermal_zone_device *zone,
|
|
int *temp)
|
|
{
|
|
struct aic_tsen_dev *tsen = zone->devdata;
|
|
|
|
*temp = tsen_get_temp(tsen, AIC_TSEN_CH_GPAI);
|
|
return 0;
|
|
}
|
|
|
|
static int tsen_cpu_get_trip_type(struct thermal_zone_device *zone, int trip,
|
|
enum thermal_trip_type *type)
|
|
{
|
|
struct aic_tsen_dev *tsen = zone->devdata;
|
|
struct device *dev = &tsen->pdev->dev;
|
|
|
|
switch (trip) {
|
|
case 0:
|
|
*type = THERMAL_TRIP_CRITICAL;
|
|
break;
|
|
default:
|
|
dev_err(dev, "driver trip error\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsen_cpu_get_trip_temp(struct thermal_zone_device *zone, int trip,
|
|
int *temp)
|
|
{
|
|
struct aic_tsen_dev *tsen = zone->devdata;
|
|
struct device *dev = &tsen->pdev->dev;
|
|
|
|
switch (trip) {
|
|
case 0:
|
|
*temp = tsen_data2temp(AIC_TSEN_CH_CPU,
|
|
tsen->chan[0].hta_thd,
|
|
&tsen->data->ch[AIC_TSEN_CH_CPU]);
|
|
break;
|
|
default:
|
|
dev_err(dev, "driver trip error\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tsen_cpu_notify(struct thermal_zone_device *zone,
|
|
int trip, enum thermal_trip_type type)
|
|
{
|
|
struct aic_tsen_dev *tsen = zone->devdata;
|
|
struct device *dev = &tsen->pdev->dev;
|
|
|
|
switch (type) {
|
|
case THERMAL_TRIP_CRITICAL:
|
|
if (tsen->chan[0].htp_enable)
|
|
dev_warn(dev,
|
|
"Thermal reached to critical temperature\n");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct thermal_zone_device_ops tsen_cpu_ops = {
|
|
.get_temp = tsen_cpu_get_temp,
|
|
.get_trip_type = tsen_cpu_get_trip_type,
|
|
.get_trip_temp = tsen_cpu_get_trip_temp,
|
|
.notify = tsen_cpu_notify,
|
|
};
|
|
|
|
static struct thermal_zone_device_ops tsen_gpai_ops = {
|
|
.get_temp = tsen_gpai_get_temp,
|
|
};
|
|
|
|
static ssize_t status_show(struct device *dev,
|
|
struct device_attribute *devattr,
|
|
char *buf)
|
|
{
|
|
int i;
|
|
int mcr;
|
|
int version;
|
|
int val[AIC_TSEN_MAX_CH];
|
|
struct aic_tsen_dev *tsen = dev_get_drvdata(dev);
|
|
struct aic_tsen_plat_data *data = tsen->data;
|
|
|
|
for (i = 0; i < 2; i++)
|
|
val[i] = tsen_get_temp(tsen, i);
|
|
|
|
mcr = readl(tsen->regs + TSEN_MCR);
|
|
version = readl(tsen->regs + TSEN_VERSION);
|
|
|
|
return sprintf(buf, "In Thermal Sensor V%d.%02d:\n"
|
|
"Number: %d/%d\n"
|
|
"Ch %-13s Mode En Value LTA HTA OTP Slope Offset\n"
|
|
"0 %-13s %4s %2d %5d %4d %4d %4d %5d %-8d\n"
|
|
"1 %-13s %4s %2d %5d %4d %4d %4d %5d %-8d\n",
|
|
version >> 8, version & 0xff,
|
|
data->num, tsen_ch_num_read(tsen->regs), "Name",
|
|
data->ch[0].name, tsen->chan[0].mode ? "P" : "S",
|
|
mcr & TSEN_MCR_CH_EN(0) ? 1 : 0, val[0],
|
|
tsen->chan[0].lta_thd, tsen->chan[0].hta_thd,
|
|
tsen->chan[0].otp_thd,
|
|
data->ch[0].slope, data->ch[0].offset,
|
|
data->ch[1].name, tsen->chan[1].mode ? "P" : "S",
|
|
mcr & TSEN_MCR_CH_EN(1) ? 1 : 0, val[1],
|
|
tsen->chan[1].lta_thd, tsen->chan[1].hta_thd,
|
|
tsen->chan[1].otp_thd,
|
|
data->ch[1].slope, data->ch[1].offset);
|
|
}
|
|
static DEVICE_ATTR_RO(status);
|
|
|
|
static struct attribute *aic_tsen_attr[] = {
|
|
&dev_attr_status.attr,
|
|
NULL
|
|
};
|
|
|
|
static irqreturn_t aic_tsen_irq_handle(int irq, void *dev_id)
|
|
{
|
|
struct aic_tsen_dev *tsen = (struct aic_tsen_dev *)dev_id;
|
|
struct device *dev = &tsen->pdev->dev;
|
|
struct aic_tsen_ch_dat *dat = tsen->data->ch;
|
|
void __iomem *regs = tsen->regs;
|
|
int i, status, detail;
|
|
struct aic_tsen_ch *chan = NULL;
|
|
|
|
spin_lock(&user_lock);
|
|
|
|
status = readl(regs + TSEN_INTR);
|
|
for (i = 0; i < tsen->ch_num; i++) {
|
|
if (!(status & TSEN_INTR_CH_INT_FLAG(i)))
|
|
continue;
|
|
|
|
chan = &tsen->chan[i];
|
|
detail = readl(regs + TSENn_INT(i));
|
|
writel(detail, regs + TSENn_INT(i));
|
|
if (detail & TSENn_INT_DAT_RDY_FLAG) {
|
|
chan->latest_data = readl(regs + TSENn_DATA(i));
|
|
dev_dbg(dev, "ch%d data %d\n", i, chan->latest_data);
|
|
complete(&chan->complete);
|
|
}
|
|
|
|
if (detail & TSENn_INT_LTA_VALID_FLAG)
|
|
dev_warn(dev, "LTA: ch%d %d(%d)!\n", i,
|
|
tsen_data2temp(i, chan->latest_data, &dat[i]),
|
|
chan->latest_data);
|
|
if (detail & TSENn_INT_LTA_RM_FLAG)
|
|
dev_warn(dev, "LTA removed: ch%d %d(%d)\n", i,
|
|
tsen_data2temp(i, chan->latest_data, &dat[i]),
|
|
chan->latest_data);
|
|
if (detail & TSENn_INT_HTA_VALID_FLAG) {
|
|
dev_warn(dev, "HTA: ch%d %d(%d)!\n", i,
|
|
tsen_data2temp(i, chan->latest_data, &dat[i]),
|
|
chan->latest_data);
|
|
spin_unlock(&user_lock);
|
|
return IRQ_WAKE_THREAD;
|
|
}
|
|
if (detail & TSENn_INT_HTA_RM_FLAG)
|
|
dev_warn(dev, "HTA removed: ch%d %d(%d)\n", i,
|
|
tsen_data2temp(i, chan->latest_data, &dat[i]),
|
|
chan->latest_data);
|
|
if (tsen_support_otp(tsen) && (detail & TSENn_INT_OTP_FLAG))
|
|
dev_warn(dev, "OTP: ch%d %d(%d)!\n", i,
|
|
tsen_data2temp(i, chan->latest_data, &dat[i]),
|
|
chan->latest_data);
|
|
}
|
|
|
|
spin_unlock(&user_lock);
|
|
dev_dbg(dev, "IRQ status %#x, detail %#x\n", status, detail);
|
|
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
static irqreturn_t aic_tsen_irq_thread(int irq, void *dev_id)
|
|
{
|
|
struct aic_tsen_dev *tsen = (struct aic_tsen_dev *)dev_id;
|
|
|
|
if (tsen->chan[0].htp_enable)
|
|
thermal_zone_device_update(tsen->chan[0].zone,
|
|
THERMAL_EVENT_UNSPECIFIED);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int __maybe_unused spear_thermal_suspend(struct device *dev)
|
|
{
|
|
struct aic_tsen_dev *tsen = dev_get_drvdata(dev);
|
|
|
|
tsen_enable(tsen->regs, 0);
|
|
|
|
reset_control_assert(tsen->rst);
|
|
clk_disable_unprepare(tsen->clk);
|
|
dev_info(dev, "Suspended.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused spear_thermal_resume(struct device *dev)
|
|
{
|
|
struct aic_tsen_dev *tsen = dev_get_drvdata(dev);
|
|
int ret = 0;
|
|
|
|
ret = clk_prepare_enable(tsen->clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = reset_control_deassert(tsen->rst);
|
|
if (ret)
|
|
return ret;
|
|
|
|
tsen_enable(tsen->regs, 1);
|
|
dev_info(dev, "Resumed.\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(aic_tsen_pm_ops, spear_thermal_suspend,
|
|
spear_thermal_resume);
|
|
|
|
static int tsen_parse_dt(struct aic_tsen_dev *tsen)
|
|
{
|
|
struct device *dev = &tsen->pdev->dev;
|
|
struct device_node *child, *np = dev->of_node;
|
|
struct aic_tsen_ch_dat *dat = tsen->data->ch;
|
|
s32 ret = 0, hta = 0;
|
|
u32 val = 0, i = 0;
|
|
|
|
for_each_child_of_node(np, child) {
|
|
struct aic_tsen_ch *chan = &tsen->chan[i];
|
|
|
|
chan->available = of_device_is_available(child);
|
|
if (!chan->available) {
|
|
dev_warn(dev, "ch%d is unavailable.\n", i);
|
|
i++;
|
|
continue;
|
|
}
|
|
dev_dbg(dev, "ch%d is available\n", i);
|
|
|
|
ret = of_property_read_u32(child, "aic,sample-period", &val);
|
|
if (ret || val == 0) {
|
|
dev_info(dev, "ch%d is single sample mode\n", i);
|
|
chan->mode = AIC_TSEN_MODE_SINGLE;
|
|
i++;
|
|
continue;
|
|
}
|
|
chan->mode = AIC_TSEN_MODE_PERIOD;
|
|
chan->smp_period = tsen_sec2itv(tsen, val);
|
|
|
|
ret = of_property_read_u32(child, "aic,high-temp-thd", &hta);
|
|
if (ret || hta == 0) {
|
|
dev_warn(dev, "Invalid ch%d HTA thd, return %d\n",
|
|
i, ret ? ret : hta);
|
|
chan->hta_enable = false;
|
|
} else {
|
|
chan->hta_enable = true;
|
|
chan->hta_thd = tsen_temp2data(i, hta, &dat[i]);
|
|
chan->hta_rm_thd = tsen_temp2data(i,
|
|
TSENn_HTA_RM_THD(hta), &dat[i]);
|
|
}
|
|
|
|
ret = of_property_read_u32(child, "aic,low-temp-thd", &val);
|
|
if (ret || val == 0 || val >= hta) {
|
|
dev_warn(dev, "Invalid ch%d LTA thd, return %d\n",
|
|
i, ret ? ret : val);
|
|
chan->lta_enable = false;
|
|
} else {
|
|
chan->lta_enable = true;
|
|
chan->lta_thd = tsen_temp2data(i, val, &dat[i]);
|
|
chan->lta_rm_thd = tsen_temp2data(i,
|
|
TSENn_LTA_RM_THD(val), &dat[i]);
|
|
}
|
|
|
|
if (chan->hta_enable) {
|
|
chan->htp_enable = of_property_read_bool(child,
|
|
"aic,htp-enable");
|
|
} else {
|
|
chan->htp_enable = false;
|
|
}
|
|
|
|
if (!tsen_support_otp(tsen)) {
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
ret = of_property_read_u32(child, "aic,over-temp-thd", &val);
|
|
if (ret || val == 0 || val < hta) {
|
|
dev_warn(dev, "Invalid ch%d OTP thd %d\n",
|
|
i, ret ? ret : val);
|
|
chan->otp_enable = false;
|
|
} else {
|
|
chan->otp_enable = true;
|
|
chan->otp_thd = tsen_temp2data(i, val, &dat[i]);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
/* For temperature's slope and offset calculated by y=kx+b equation.
|
|
* THS0_ADC_VAL as y, THS_ENV_TEMP as x.
|
|
*/
|
|
static void aic_tsen_get_cali_param(int ch_id, int y1, int y2, int x1, int x2,
|
|
struct aic_tsen_ch_dat *dat)
|
|
{
|
|
int slope, offset;
|
|
int calib_scale;
|
|
|
|
calib_scale = THERMAL_CORE_TEMP_AMPN_SCALE * TSEN_CALIB_ACCURACY_SCALE;
|
|
if ((x2 - x1) != 0) {
|
|
slope = (y1 - y2) * calib_scale / (x2 - x1);
|
|
offset = y1 * calib_scale - slope * x1;
|
|
dat[ch_id].offset = offset;
|
|
dat[ch_id].slope = slope;
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* The temperature obtained from nvmem contains sign bits.
|
|
* For this purpose, this function converts data through sign bit mask
|
|
*/
|
|
static int aic_tsen_env_temp_cali(u8 sign_mask, u8 val)
|
|
{
|
|
if (val & sign_mask)
|
|
return -(val & (sign_mask - 1));
|
|
else
|
|
return val & (sign_mask - 1);
|
|
}
|
|
|
|
static int aic_tsen_get_nvmem_cell(struct aic_tsen_dev *tsen)
|
|
{
|
|
int i, ret = 0;
|
|
size_t len = 0;
|
|
struct device *dev = &tsen->pdev->dev;
|
|
char *cell_name[TSEN_NVMEM_CELL_NUM] = {"t0_low", "t1_low", "t0_high",
|
|
"t1_high", "envtemp_low",
|
|
"envtemp_high",
|
|
"ldo30_bg_ctrl", "cp_version"};
|
|
|
|
for (i = 0; i < TSEN_NVMEM_CELL_NUM ; i++) {
|
|
struct nvmem_cell *cell = NULL;
|
|
void *data = NULL;
|
|
|
|
cell = devm_nvmem_cell_get(dev, cell_name[i]);
|
|
if (IS_ERR(cell)) {
|
|
dev_info(dev, "Failed to get cell\n");
|
|
ret = PTR_ERR(cell);
|
|
return -1;
|
|
}
|
|
|
|
data = nvmem_cell_read(cell, &len);
|
|
if (IS_ERR(data)) {
|
|
dev_info(dev, "Failed to read cell %s: %ld\n", cell_name[i], PTR_ERR(data));
|
|
ret = PTR_ERR(data);
|
|
break;
|
|
}
|
|
|
|
if (len != sizeof(int)) {
|
|
dev_info(dev, "Unexpected data length for cell %s: %zu\n", cell_name[i],
|
|
len);
|
|
kfree(data);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
tsen->cell_data[i] = *(int *)data;
|
|
if (tsen->cell_data[i] == 0)
|
|
dev_info(dev, "Efuse%d didn't burn calibration value\n", i);
|
|
|
|
kfree(data);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#if 0
|
|
static int aic_tsen_double_point_cali(struct aic_tsen_dev *tsen)
|
|
{
|
|
struct aic_tsen_ch_dat *dat = tsen->data->ch;
|
|
|
|
int env_temp_low = TSEN_ENV_TEMP_LOW_BASE;
|
|
int env_temp_high = TSEN_ENV_TEMP_HIGH_BASE;
|
|
u16 *buf = tsen->cell_data;
|
|
|
|
env_temp_low += aic_tsen_env_temp_cali(TSEN_ENV_TEMP_LOW_SIGN_MASK,
|
|
buf[TSEN_THS_ENV_TEMP_LOW]);
|
|
env_temp_high += aic_tsen_env_temp_cali(TSEN_ENV_TEMP_HIGH_SIGN_MASK,
|
|
buf[TSEN_THS_ENV_TEMP_HIGH]);
|
|
aic_tsen_get_cali_param(AIC_TSEN_CH_CPU, buf[TSEN_THS0_ADC_VAL_LOW],
|
|
buf[TSEN_THS0_ADC_VAL_HIGH], env_temp_low,
|
|
env_temp_high, dat);
|
|
aic_tsen_get_cali_param(AIC_TSEN_CH_GPAI, buf[TSEN_THS1_ADC_VAL_LOW],
|
|
buf[TSEN_THS0_ADC_VAL_HIGH], env_temp_low,
|
|
env_temp_high, dat);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int aic_tsen_single_point_cali_for_correct(struct aic_tsen_dev *tsen)
|
|
{
|
|
int i;
|
|
int origin_vol;
|
|
int origin_adc;
|
|
int cali_scale;
|
|
u32 *buf = tsen->cell_data;
|
|
u32 ldo30_bg_ctrl;
|
|
int env_temp_low = TSEN_ENV_TEMP_LOW_BASE;
|
|
int standard_vol = TSEN_ORIGIN_STANDARD_VOLTAGE;
|
|
int vol_scale_unit = TSEN_VOLTAGE_SCALE_UNIT;
|
|
struct aic_tsen_ch_dat *dat;
|
|
|
|
env_temp_low += aic_tsen_env_temp_cali(TSEN_ENV_TEMP_LOW_SIGN_MASK,
|
|
buf[TSEN_THS_ENV_TEMP_LOW]);
|
|
cali_scale = THERMAL_CORE_TEMP_AMPN_SCALE * TSEN_CALIB_ACCURACY_SCALE;
|
|
|
|
ldo30_bg_ctrl = buf[TSEN_LDO30_BG_CTRL] & TSEN_LDO30_BG_CTRL_MASK;
|
|
|
|
|
|
if (ldo30_bg_ctrl > TSEN_TRIM_VOLTAGE_BOUNDARY_VAL)
|
|
origin_vol = standard_vol - (255 - ldo30_bg_ctrl) * vol_scale_unit;
|
|
else
|
|
origin_vol = standard_vol + ldo30_bg_ctrl * vol_scale_unit;
|
|
for (i = 0; i < tsen->ch_num; i++) {
|
|
dat = &tsen->data->ch[i];
|
|
switch (i) {
|
|
case AIC_TSEN_CH_CPU:
|
|
origin_adc = origin_vol * buf[TSEN_THS0_ADC_VAL_LOW] / standard_vol;
|
|
dat->slope = TSEN_SINGLE_POINT_CALI_K_CPU;
|
|
break;
|
|
case AIC_TSEN_CH_GPAI:
|
|
origin_adc = origin_vol * buf[TSEN_THS1_ADC_VAL_LOW] / standard_vol;
|
|
dat->slope = TSEN_SINGLE_POINT_CALI_K_GPAI;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
dat->offset = env_temp_low * cali_scale - dat->slope * origin_adc;
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int aic_tsen_single_point_cali(struct aic_tsen_dev *tsen)
|
|
{
|
|
int i;
|
|
int cali_scale;
|
|
u32 *buf = tsen->cell_data;
|
|
int env_temp_low = TSEN_ENV_TEMP_LOW_BASE;
|
|
struct aic_tsen_ch_dat *dat;
|
|
|
|
env_temp_low += aic_tsen_env_temp_cali(TSEN_ENV_TEMP_LOW_SIGN_MASK,
|
|
buf[TSEN_THS_ENV_TEMP_LOW]);
|
|
cali_scale = THERMAL_CORE_TEMP_AMPN_SCALE * TSEN_CALIB_ACCURACY_SCALE;
|
|
|
|
for (i = 0; i < tsen->ch_num; i++) {
|
|
dat = &tsen->data->ch[i];
|
|
switch (i) {
|
|
case AIC_TSEN_CH_CPU:
|
|
dat->slope = TSEN_SINGLE_POINT_CALI_K_CPU;
|
|
dat->offset = env_temp_low * cali_scale - dat->slope * buf[TSEN_THS0_ADC_VAL_LOW];
|
|
break;
|
|
case AIC_TSEN_CH_GPAI:
|
|
dat->slope = TSEN_SINGLE_POINT_CALI_K_GPAI;
|
|
dat->offset = env_temp_low * cali_scale - dat->slope * buf[TSEN_THS1_ADC_VAL_LOW];
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void aic_tsen_curve_fitting(struct aic_tsen_dev *tsen)
|
|
{
|
|
int cp_version = tsen->cell_data[TSEN_CP_VERSION];
|
|
|
|
if (cp_version == 0)
|
|
return;
|
|
else if (cp_version < TSEN_CP_VERSION_DIFF_TYPE)
|
|
aic_tsen_single_point_cali_for_correct(tsen);
|
|
else
|
|
aic_tsen_single_point_cali(tsen);
|
|
}
|
|
|
|
static const struct of_device_id aic_tsen_id_table[];
|
|
|
|
static struct thermal_zone_params tsen_cpu_zone_params = {
|
|
.no_hwmon = false,
|
|
};
|
|
|
|
static struct thermal_zone_params tsen_gpai_zone_params = {
|
|
.no_hwmon = false,
|
|
};
|
|
|
|
static int aic_tsen_probe(struct platform_device *pdev)
|
|
{
|
|
struct thermal_zone_device *zone_dev = NULL;
|
|
struct aic_tsen_dev *tsen;
|
|
struct clk *clk;
|
|
int ret = 0, i;
|
|
const struct of_device_id *of_id = NULL;
|
|
struct thermal_zone_device_ops *ops[AIC_TSEN_MAX_CH] = {
|
|
&tsen_cpu_ops, &tsen_gpai_ops, NULL, NULL};
|
|
struct thermal_zone_params *params[AIC_TSEN_MAX_CH] = {
|
|
&tsen_cpu_zone_params, &tsen_gpai_zone_params, NULL, NULL};
|
|
|
|
tsen = devm_kzalloc(&pdev->dev, sizeof(*tsen), GFP_KERNEL);
|
|
if (!tsen)
|
|
return -ENOMEM;
|
|
|
|
of_id = of_match_node(aic_tsen_id_table, pdev->dev.of_node);
|
|
if (!of_id)
|
|
return -EINVAL;
|
|
tsen->data = (struct aic_tsen_plat_data *)of_id->data;
|
|
|
|
tsen->regs = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(tsen->regs))
|
|
return PTR_ERR(tsen->regs);
|
|
|
|
clk = devm_clk_get(&pdev->dev, "pclk");
|
|
if (IS_ERR(clk)) {
|
|
dev_err(&pdev->dev, "Failed to get pclk\n");
|
|
return PTR_ERR(clk);
|
|
}
|
|
tsen->pclk_rate = clk_get_rate(clk);
|
|
dev_dbg(&pdev->dev, "PCLK rate %d\n", tsen->pclk_rate);
|
|
|
|
tsen->clk = devm_clk_get(&pdev->dev, "tsen");
|
|
if (IS_ERR(tsen->clk)) {
|
|
dev_err(&pdev->dev, "no clock defined\n");
|
|
return PTR_ERR(tsen->clk);
|
|
}
|
|
ret = clk_prepare_enable(tsen->clk);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "Failed to enable clk, return %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
tsen->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
|
|
if (IS_ERR(tsen->rst)) {
|
|
ret = PTR_ERR(tsen->rst);
|
|
goto disable_clk;
|
|
}
|
|
reset_control_deassert(tsen->rst);
|
|
|
|
ret = platform_get_irq(pdev, 0);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to get irq\n");
|
|
goto disable_clk;
|
|
}
|
|
tsen->irq = ret;
|
|
ret = request_threaded_irq(tsen->irq,
|
|
aic_tsen_irq_handle, aic_tsen_irq_thread,
|
|
IRQF_ONESHOT, AIC_TSEN_NAME, tsen);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to request IRQ %d\n", tsen->irq);
|
|
goto disable_rst;
|
|
}
|
|
|
|
tsen_enable(tsen->regs, 1);
|
|
tsen->ch_num = tsen->data->num;
|
|
tsen->pdev = pdev;
|
|
tsen_parse_dt(tsen);
|
|
|
|
aic_tsen_get_nvmem_cell(tsen);
|
|
aic_tsen_curve_fitting(tsen);
|
|
|
|
for (i = 0; i < tsen->ch_num; i++) {
|
|
struct aic_tsen_ch *chan = &tsen->chan[i];
|
|
int trips[AIC_TSEN_MAX_CH] = {TSEN_CPU_ZONE_TRIPS_NUM,
|
|
TSEN_GPAI_ZONE_TRIPS_NUM};
|
|
|
|
if (!chan->available)
|
|
continue;
|
|
|
|
zone_dev = thermal_zone_device_register(tsen->data->ch[i].name,
|
|
trips[i], 0, tsen,
|
|
ops[i], params[i], 0, 0);
|
|
if (IS_ERR(zone_dev)) {
|
|
dev_err(&pdev->dev, "zone device %d failed!\n", i);
|
|
ret = PTR_ERR(zone_dev);
|
|
goto disable_rst;
|
|
}
|
|
|
|
chan->zone = zone_dev;
|
|
|
|
if (!chan->zone) {
|
|
dev_err(&pdev->dev, "Thermal zone %d device is NULL\n", i);
|
|
goto disable_rst;
|
|
}
|
|
if (!chan->zone->tzp) {
|
|
dev_err(&pdev->dev, "Thermal zone %d params is NULL\n", i);
|
|
goto disable_rst;
|
|
}
|
|
chan->zone->tzp->offset = tsen->data->ch[i].offset;
|
|
chan->zone->tzp->slope = tsen->data->ch[i].slope;
|
|
|
|
tsen_ch_init(tsen, i);
|
|
|
|
ret = thermal_zone_device_enable(zone_dev);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "zone device %d enable failed!\n", i);
|
|
thermal_zone_device_unregister(zone_dev);
|
|
zone_dev = ERR_PTR(ret);
|
|
}
|
|
}
|
|
|
|
tsen->attrs.attrs = aic_tsen_attr;
|
|
ret = sysfs_create_group(&pdev->dev.kobj, &tsen->attrs);
|
|
if (ret)
|
|
goto free_irq;
|
|
|
|
platform_set_drvdata(pdev, tsen);
|
|
dev_info(&pdev->dev, "Artinchip Thermal Sensor Loaded.\n");
|
|
|
|
return 0;
|
|
|
|
free_irq:
|
|
free_irq(tsen->irq, tsen);
|
|
disable_rst:
|
|
reset_control_assert(tsen->rst);
|
|
disable_clk:
|
|
clk_disable_unprepare(tsen->clk);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int aic_tsen_exit(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
struct aic_tsen_dev *tsen = platform_get_drvdata(pdev);
|
|
|
|
for (i = 0; i < tsen->ch_num; i++) {
|
|
tsen_ch_enable(tsen->regs, i, 0);
|
|
thermal_zone_device_unregister(tsen->chan[i].zone);
|
|
}
|
|
|
|
tsen_enable(tsen->regs, 0);
|
|
free_irq(tsen->irq, tsen);
|
|
reset_control_assert(tsen->rst);
|
|
clk_disable_unprepare(tsen->clk);
|
|
return 0;
|
|
}
|
|
|
|
static struct aic_tsen_plat_data aic_tsen_data_v01 = {2, {
|
|
{AIC_TSEN_NAME "-cpu", -1065, 2300981},
|
|
{AIC_TSEN_NAME "-gpai", -1070, 2312717}}
|
|
};
|
|
|
|
static struct aic_tsen_plat_data aic_tsen_data_v10 = {2, {
|
|
{AIC_TSEN_NAME "-cpu", -1134, 2439001},
|
|
{AIC_TSEN_NAME "-gpai", -1139, 2450566}}
|
|
};
|
|
|
|
static const struct of_device_id aic_tsen_id_table[] = {
|
|
{
|
|
.compatible = "artinchip,aic-tsen-v0.1",
|
|
.data = &aic_tsen_data_v01,
|
|
}, {
|
|
.compatible = "artinchip,aic-tsen-v1.0",
|
|
.data = &aic_tsen_data_v10,
|
|
},
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, aic_tsen_id_table);
|
|
|
|
static struct platform_driver aic_tsen_driver = {
|
|
.probe = aic_tsen_probe,
|
|
.remove = aic_tsen_exit,
|
|
.driver = {
|
|
.name = AIC_TSEN_NAME,
|
|
.pm = &aic_tsen_pm_ops,
|
|
.of_match_table = aic_tsen_id_table,
|
|
},
|
|
};
|
|
module_platform_driver(aic_tsen_driver);
|
|
|
|
MODULE_AUTHOR("Matteo <duanmt@artinchip.com>");
|
|
MODULE_DESCRIPTION("Thermal Sensor driver of Artinchip SoC");
|
|
MODULE_LICENSE("GPL");
|