mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 19:30:30 +09:00
thermal: G12A: tsensor and cooldev driver.
PD#156734: add thermal tsensor and cooling devices driver. Change-Id: I22ea0e03692fbcf7da269330fde86e07f189c4e9 Signed-off-by: huan.biao <huan.biao@amlogic.com>
This commit is contained in:
@@ -14350,3 +14350,8 @@ AMLOGIC Irblaster driver
|
||||
M: yu.tu <yu.tu@amlogic.com>
|
||||
F: drivers/amlogic/irblaster/meson-irblaster.c
|
||||
F: drivers/amlogic/irblaster/meson-irblaster.h
|
||||
|
||||
AMLOGIC THERMAL DRIVER
|
||||
M: Huan Biao <huan.biao@amlogic.com>
|
||||
F: drivers/amlgoic/thermal/meson_tsensor.c
|
||||
F: drivers/amlogic/thermal/meson_cooldev.c
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
obj-$(CONFIG_AMLOGIC_GX_TEMP_SENSOR) += aml_thermal_hw.o
|
||||
obj-$(CONFIG_AMLOGIC_M8B_TEMP_SENSOR) += aml_thermal_hw_m8b.o
|
||||
obj-$(CONFIG_AMLOGIC_GX_TEMP_SENSOR) += meson_tsensor.o
|
||||
obj-$(CONFIG_AMLOGIC_GX_TEMP_SENSOR) += meson_cooldev.o
|
||||
obj-$(CONFIG_AMLOGIC_CPUCORE_THERMAL) += cpucore_cooling.o
|
||||
obj-$(CONFIG_AMLOGIC_GPU_THERMAL) += gpu_cooling.o
|
||||
obj-$(CONFIG_AMLOGIC_GPUCORE_THERMAL) += gpucore_cooling.o
|
||||
|
||||
373
drivers/amlogic/thermal/meson_cooldev.c
Normal file
373
drivers/amlogic/thermal/meson_cooldev.c
Normal file
@@ -0,0 +1,373 @@
|
||||
/*
|
||||
* drivers/amlogic/thermal/meson_cooldev.c
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. 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 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/amlogic/cpu_version.h>
|
||||
#include <linux/amlogic/scpi_protocol.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/cpu_cooling.h>
|
||||
#include <linux/amlogic/cpucore_cooling.h>
|
||||
#include <linux/amlogic/gpucore_cooling.h>
|
||||
#include <linux/amlogic/gpu_cooling.h>
|
||||
#include <linux/cpu.h>
|
||||
|
||||
enum cluster_type {
|
||||
CLUSTER_BIG = 0,
|
||||
CLUSTER_LITTLE,
|
||||
NUM_CLUSTERS
|
||||
};
|
||||
|
||||
enum cool_dev_type {
|
||||
COOL_DEV_TYPE_CPU_FREQ = 0,
|
||||
COOL_DEV_TYPE_CPU_CORE,
|
||||
COOL_DEV_TYPE_GPU_FREQ,
|
||||
COOL_DEV_TYPE_GPU_CORE,
|
||||
COOL_DEV_TYPE_MAX
|
||||
};
|
||||
|
||||
struct cool_dev {
|
||||
int min_state;
|
||||
int coeff;
|
||||
int cluster_id;
|
||||
char *device_type;
|
||||
struct device_node *np;
|
||||
struct thermal_cooling_device *cooling_dev;
|
||||
};
|
||||
|
||||
struct meson_cooldev {
|
||||
int chip_trimmed;
|
||||
int cool_dev_num;
|
||||
int min_exist;
|
||||
struct mutex lock;
|
||||
struct cpumask mask[NUM_CLUSTERS];
|
||||
struct cool_dev *cool_devs;
|
||||
struct thermal_zone_device *tzd;
|
||||
};
|
||||
|
||||
static int get_cool_dev_type(char *type)
|
||||
{
|
||||
if (!strcmp(type, "cpufreq"))
|
||||
return COOL_DEV_TYPE_CPU_FREQ;
|
||||
if (!strcmp(type, "cpucore"))
|
||||
return COOL_DEV_TYPE_CPU_CORE;
|
||||
if (!strcmp(type, "gpufreq"))
|
||||
return COOL_DEV_TYPE_GPU_FREQ;
|
||||
if (!strcmp(type, "gpucore"))
|
||||
return COOL_DEV_TYPE_GPU_CORE;
|
||||
return COOL_DEV_TYPE_MAX;
|
||||
}
|
||||
|
||||
static struct cool_dev *get_cool_dev_by_node(struct platform_device *pdev,
|
||||
struct device_node *np)
|
||||
{
|
||||
struct meson_cooldev *mcooldev = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
struct cool_dev *dev;
|
||||
|
||||
if (!np)
|
||||
return NULL;
|
||||
for (i = 0; i < mcooldev->cool_dev_num; i++) {
|
||||
dev = &mcooldev->cool_devs[i];
|
||||
if (dev->np == np)
|
||||
return dev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int meson_set_min_status(struct thermal_cooling_device *cdev,
|
||||
unsigned long min_state)
|
||||
{
|
||||
struct device_node *tzdnp, *child, *coolmap, *gchild;
|
||||
struct thermal_zone_device *tzd = ERR_PTR(-ENODEV);
|
||||
struct device_node *np = cdev->np;
|
||||
int err = 0;
|
||||
|
||||
tzdnp = of_find_node_by_name(NULL, "thermal-zones");
|
||||
if (!tzdnp)
|
||||
goto end;
|
||||
for_each_available_child_of_node(tzdnp, child) {
|
||||
coolmap = of_find_node_by_name(child, "cooling-maps");
|
||||
for_each_available_child_of_node(coolmap, gchild) {
|
||||
struct of_phandle_args cooling_spec;
|
||||
int ret;
|
||||
|
||||
ret = of_parse_phandle_with_args(
|
||||
gchild,
|
||||
"cooling-device",
|
||||
"#cooling-cells",
|
||||
0,
|
||||
&cooling_spec);
|
||||
if (ret < 0) {
|
||||
pr_err("missing cooling_device property\n");
|
||||
goto end;
|
||||
}
|
||||
if (cooling_spec.np == np) {
|
||||
int i;
|
||||
|
||||
tzd =
|
||||
thermal_zone_get_zone_by_name(child->name);
|
||||
pr_info("find tzd id: %d\n", tzd->id);
|
||||
for (i = 0; i < tzd->trips; i++)
|
||||
thermal_set_upper(tzd,
|
||||
cdev, i, min_state);
|
||||
err = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
end:
|
||||
return err;
|
||||
}
|
||||
|
||||
int meson_cooldev_min_update(struct platform_device *pdev, int index)
|
||||
{
|
||||
struct meson_cooldev *mcooldev = platform_get_drvdata(pdev);
|
||||
struct cool_dev *cool = &mcooldev->cool_devs[index];
|
||||
struct thermal_cooling_device *cdev = cool->cooling_dev;
|
||||
struct gpufreq_cooling_device *gf_cdev;
|
||||
struct gpucore_cooling_device *gc_cdev;
|
||||
long min_state;
|
||||
int ret;
|
||||
int cpu, c_id;
|
||||
|
||||
cool = get_cool_dev_by_node(pdev, cdev->np);
|
||||
if (!cool)
|
||||
return -ENODEV;
|
||||
|
||||
if (cool->cooling_dev == NULL)
|
||||
cool->cooling_dev = cdev;
|
||||
|
||||
if (cool->min_state == 0)
|
||||
return 0;
|
||||
|
||||
switch (get_cool_dev_type(cool->device_type)) {
|
||||
case COOL_DEV_TYPE_CPU_CORE:
|
||||
/* TODO: cluster ID */
|
||||
cool->cooling_dev->ops->get_max_state(cdev, &min_state);
|
||||
min_state = min_state - cool->min_state;
|
||||
break;
|
||||
|
||||
case COOL_DEV_TYPE_CPU_FREQ:
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (mc_capable())
|
||||
c_id = topology_physical_package_id(cpu);
|
||||
else
|
||||
c_id = 0; /* force cluster 0 if no MC */
|
||||
if (c_id == cool->cluster_id)
|
||||
break;
|
||||
}
|
||||
min_state = cpufreq_cooling_get_level(cpu, cool->min_state);
|
||||
break;
|
||||
|
||||
case COOL_DEV_TYPE_GPU_CORE:
|
||||
gc_cdev = (struct gpucore_cooling_device *)cdev->devdata;
|
||||
cdev->ops->get_max_state(cdev, &min_state);
|
||||
min_state = min_state - cool->min_state;
|
||||
break;
|
||||
|
||||
case COOL_DEV_TYPE_GPU_FREQ:
|
||||
gf_cdev = (struct gpufreq_cooling_device *)cdev->devdata;
|
||||
min_state = gf_cdev->get_gpu_freq_level(cool->min_state);
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = meson_set_min_status(cdev, min_state);
|
||||
if (!ret)
|
||||
pr_info("meson_cdev set min sussces\n");
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(meson_cooldev_min_update);
|
||||
|
||||
|
||||
static int register_cool_dev(struct platform_device *pdev, int index)
|
||||
{
|
||||
struct meson_cooldev *mcooldev = platform_get_drvdata(pdev);
|
||||
struct cool_dev *cool = &mcooldev->cool_devs[index];
|
||||
int pp;
|
||||
struct cpumask *mask;
|
||||
int id = cool->cluster_id;
|
||||
|
||||
pr_info("meson_cdev index: %d\n", index);
|
||||
switch (get_cool_dev_type(cool->device_type)) {
|
||||
case COOL_DEV_TYPE_CPU_CORE:
|
||||
cool->cooling_dev = cpucore_cooling_register(cool->np,
|
||||
cool->cluster_id);
|
||||
break;
|
||||
|
||||
case COOL_DEV_TYPE_CPU_FREQ:
|
||||
mask = &mcooldev->mask[id];
|
||||
cool->cooling_dev = of_cpufreq_power_cooling_register(cool->np,
|
||||
mask,
|
||||
cool->coeff,
|
||||
NULL);
|
||||
break;
|
||||
/* GPU is KO, just save these parameters */
|
||||
case COOL_DEV_TYPE_GPU_FREQ:
|
||||
if (of_property_read_u32(cool->np, "num_of_pp", &pp))
|
||||
pr_err("thermal: read num_of_pp failed\n");
|
||||
save_gpu_cool_para(cool->coeff, cool->np, pp);
|
||||
return 0;
|
||||
|
||||
case COOL_DEV_TYPE_GPU_CORE:
|
||||
save_gpucore_thermal_para(cool->np);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
pr_err("thermal: unknown type:%s\n", cool->device_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (IS_ERR(cool->cooling_dev)) {
|
||||
pr_err("thermal: register %s failed\n", cool->device_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_cool_device(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_cooldev *mcooldev = platform_get_drvdata(pdev);
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int i, temp, ret = 0;
|
||||
struct cool_dev *cool;
|
||||
struct device_node *node, *child;
|
||||
const char *str;
|
||||
|
||||
child = of_get_child_by_name(np, "cooling_devices");
|
||||
if (child == NULL) {
|
||||
pr_err("meson cooldev: can't found cooling_devices\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
mcooldev->cool_dev_num = of_get_child_count(child);
|
||||
i = sizeof(struct cool_dev) * mcooldev->cool_dev_num;
|
||||
mcooldev->cool_devs = kzalloc(i, GFP_KERNEL);
|
||||
if (mcooldev->cool_devs == NULL) {
|
||||
pr_err("meson cooldev: alloc mem failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
child = of_get_next_child(child, NULL);
|
||||
for (i = 0; i < mcooldev->cool_dev_num; i++) {
|
||||
cool = &mcooldev->cool_devs[i];
|
||||
if (child == NULL)
|
||||
break;
|
||||
if (of_property_read_u32(child, "min_state", &temp))
|
||||
pr_err("thermal: read min_state failed\n");
|
||||
else
|
||||
cool->min_state = temp;
|
||||
|
||||
if (of_property_read_u32(child, "dyn_coeff", &temp))
|
||||
pr_err("thermal: read dyn_coeff failed\n");
|
||||
else
|
||||
cool->coeff = temp;
|
||||
|
||||
if (of_property_read_u32(child, "cluster_id", &temp))
|
||||
pr_err("thermal: read cluster_id failed\n");
|
||||
else
|
||||
cool->cluster_id = temp;
|
||||
|
||||
if (of_property_read_string(child, "device_type", &str))
|
||||
pr_err("thermal: read device_type failed\n");
|
||||
else
|
||||
cool->device_type = (char *)str;
|
||||
|
||||
if (of_property_read_string(child, "node_name", &str))
|
||||
pr_err("thermal: read node_name failed\n");
|
||||
else {
|
||||
node = of_find_node_by_name(NULL, str);
|
||||
if (!node)
|
||||
pr_err("thermal: can't find node\n");
|
||||
cool->np = node;
|
||||
}
|
||||
if (cool->np)
|
||||
ret += register_cool_dev(pdev, i);
|
||||
child = of_get_next_child(np, child);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int meson_cooldev_probe(struct platform_device *pdev)
|
||||
{
|
||||
int cpu, i, c_id;
|
||||
struct cool_dev *cool;
|
||||
struct meson_cooldev *mcooldev;
|
||||
|
||||
pr_info("meson_cdev probe\n");
|
||||
mcooldev = devm_kzalloc(&pdev->dev, sizeof(struct meson_cooldev),
|
||||
GFP_KERNEL);
|
||||
if (!mcooldev)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, mcooldev);
|
||||
mutex_init(&mcooldev->lock);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
if (mc_capable())
|
||||
c_id = topology_physical_package_id(cpu);
|
||||
else
|
||||
c_id = CLUSTER_BIG; /* Always cluster 0 if no mc */
|
||||
if (c_id > NUM_CLUSTERS) {
|
||||
pr_err("Cluster id: %d > %d\n", c_id, NUM_CLUSTERS);
|
||||
return -EINVAL;
|
||||
}
|
||||
cpumask_set_cpu(cpu, &mcooldev->mask[c_id]);
|
||||
}
|
||||
|
||||
if (parse_cool_device(pdev))
|
||||
return -EINVAL;
|
||||
|
||||
/* update min state for each device */
|
||||
for (i = 0; i < mcooldev->cool_dev_num; i++) {
|
||||
cool = &mcooldev->cool_devs[i];
|
||||
if (cool->cooling_dev)
|
||||
meson_cooldev_min_update(pdev, i);
|
||||
}
|
||||
pr_info("meson_cdev probe done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_cooldev_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_cooldev *mcooldev = platform_get_drvdata(pdev);
|
||||
devm_kfree(&pdev->dev, mcooldev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id meson_cooldev_of_match[] = {
|
||||
{ .compatible = "amlogic, meson-cooldev" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver meson_cooldev_platdrv = {
|
||||
.driver = {
|
||||
.name = "meson-cooldev",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = meson_cooldev_of_match,
|
||||
},
|
||||
.probe = meson_cooldev_probe,
|
||||
.remove = meson_cooldev_remove,
|
||||
};
|
||||
|
||||
static int __init meson_cooldev_platdrv_init(void)
|
||||
{
|
||||
return platform_driver_register(&(meson_cooldev_platdrv));
|
||||
}
|
||||
late_initcall(meson_cooldev_platdrv_init);
|
||||
794
drivers/amlogic/thermal/meson_tsensor.c
Normal file
794
drivers/amlogic/thermal/meson_tsensor.c
Normal file
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
* drivers/amlogic/thermal/meson_tsensor.c
|
||||
*
|
||||
* Copyright (C) 2017 Amlogic, Inc. 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 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.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/cpu_cooling.h>
|
||||
|
||||
#include "../../thermal/thermal_core.h"
|
||||
|
||||
//#define MESON_G12_PTM
|
||||
|
||||
#define MESON_TS_DEBUG_INFO
|
||||
|
||||
/*r1p1 thermal sensor version*/
|
||||
#define R1P1_TS_CFG_REG1 (0x1 * 4)
|
||||
#define R1P1_TS_CFG_REG2 (0x2 * 4)
|
||||
#define R1P1_TS_CFG_REG3 (0x3 * 4)
|
||||
#define R1P1_TS_CFG_REG4 (0x4 * 4)
|
||||
#define R1P1_TS_CFG_REG5 (0x5 * 4)
|
||||
#define R1P1_TS_CFG_REG6 (0x6 * 4)
|
||||
#define R1P1_TS_CFG_REG7 (0x7 * 4)
|
||||
#define R1P1_TS_CFG_REG8 (0x8 * 4)
|
||||
#define R1P1_TS_STAT0 (0x10 * 4)
|
||||
#define R1P1_TS_STAT1 (0x11 * 4)
|
||||
#define R1P1_TS_STAT2 (0x12 * 4)
|
||||
#define R1P1_TS_STAT3 (0x13 * 4)
|
||||
#define R1P1_TS_STAT4 (0x14 * 4)
|
||||
#define R1P1_TS_STAT5 (0x15 * 4)
|
||||
#define R1P1_TS_STAT6 (0x16 * 4)
|
||||
#define R1P1_TS_STAT7 (0x17 * 4)
|
||||
#define R1P1_TS_STAT8 (0x18 * 4)
|
||||
#define R1P1_TS_STAT9 (0x19 * 4)
|
||||
|
||||
#define R1P1_TS_VALUE_CONT 0x10
|
||||
#define R1P1_TRIM_INFO 0x0
|
||||
#define R1P1_TS_TEMP_MASK 0xfff
|
||||
#define R1P1_TS_IRQ_MASK 0xff
|
||||
|
||||
#define R1P1_TS_IRQ_LOGIC_EN_SHIT 15
|
||||
|
||||
#define R1P1_TS_IRQ_FALL3_EN_SHIT 31
|
||||
#define R1P1_TS_IRQ_FALL2_EN_SHIT 30
|
||||
#define R1P1_TS_IRQ_FALL1_EN_SHIT 29
|
||||
#define R1P1_TS_IRQ_FALL0_EN_SHIT 28
|
||||
#define R1P1_TS_IRQ_RISE3_EN_SHIT 27
|
||||
#define R1P1_TS_IRQ_RISE2_EN_SHIT 26
|
||||
#define R1P1_TS_IRQ_RISE1_EN_SHIT 25
|
||||
#define R1P1_TS_IRQ_RISE0_EN_SHIT 24
|
||||
#define R1P1_TS_IRQ_FALL3_CLR_SHIT 23
|
||||
#define R1P1_TS_IRQ_FALL2_CLR_SHIT 22
|
||||
#define R1P1_TS_IRQ_FALL1_CLR_SHIT 21
|
||||
#define R1P1_TS_IRQ_FALL0_CLR_SHIT 20
|
||||
#define R1P1_TS_IRQ_RISE3_CLR_SHIT 19
|
||||
#define R1P1_TS_IRQ_RISE2_CLR_SHIT 18
|
||||
#define R1P1_TS_IRQ_RISE1_CLR_SHIT 17
|
||||
#define R1P1_TS_IRQ_RISE0_CLR_SHIT 16
|
||||
#define R1P1_TS_IRQ_ALL_CLR (0xff << 16)
|
||||
#define R1P1_TS_IRQ_ALL_EN (0xff << 24)
|
||||
#define R1P1_TS_IRQ_ALL_CLR_SHIT 16
|
||||
|
||||
#define R1P1_TS_RSET_VBG BIT(12)
|
||||
#define R1P1_TS_RSET_ADC BIT(11)
|
||||
#define R1P1_TS_VCM_EN BIT(10)
|
||||
#define R1P1_TS_VBG_EN BIT(9)
|
||||
#define R1P1_TS_OUT_CTL BIT(6)
|
||||
#define R1P1_TS_FILTER_EN BIT(5)
|
||||
#define R1P1_TS_IPTAT_EN BIT(4) /*for debug, no need enable*/
|
||||
#define R1P1_TS_DEM_EN BIT(3)
|
||||
#define R1P1_TS_CH_SEL 0x3 /*set 3'b011 for work*/
|
||||
|
||||
#define R1P1_TS_HITEMP_EN BIT(31)
|
||||
#define R1P1_TS_REBOOT_ALL_EN BIT(30)
|
||||
#define R1P1_TS_REBOOT_TIME (0xff << 16)
|
||||
|
||||
/*for all thermal sensor*/
|
||||
#define MCELSIUS 1000
|
||||
#define MAX_TS_NUM 3
|
||||
#define TS_DEF_RTEMP 125
|
||||
|
||||
|
||||
enum soc_type {
|
||||
SOC_ARCH_TS_R1P0 = 1,
|
||||
SOC_ARCH_TS_R1P1 = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct meson_tsensor_platform_data
|
||||
* @gain: gain of amplifier in the positive-TC generator block
|
||||
* 0 < gain <= 15
|
||||
* @reference_voltage: reference voltage of amplifier
|
||||
* in the positive-TC generator block
|
||||
* 0 < reference_voltage <= 31
|
||||
* @noise_cancel_mode: noise cancellation mode
|
||||
* 000, 100, 101, 110 and 111 can be different modes
|
||||
* @type: determines the type of SOC
|
||||
* @efuse_value: platform defined fuse value
|
||||
* @min_efuse_value: minimum valid trimming data
|
||||
* @max_efuse_value: maximum valid trimming data
|
||||
* @default_temp_offset: default temperature offset in case of no trimming
|
||||
* @cal_type: calibration type for temperature
|
||||
*
|
||||
* This structure is required for configuration of exynos_tmu driver.
|
||||
*/
|
||||
struct meson_tsensor_platform_data {
|
||||
u32 cal_type;
|
||||
int cal_a;
|
||||
int cal_b;
|
||||
int cal_c;
|
||||
int cal_d;
|
||||
int ctl_data;
|
||||
int reboot_temp;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct meson_tsensor_data :
|
||||
* A structure to hold the private data of the tsensor driver.
|
||||
* @id: identifier of the one instance of the tsensor controller.
|
||||
* @pdata: pointer to the tmu platform/configuration data
|
||||
* @base: base address of the single instance of the tsensor controller.
|
||||
* @base_second: base address of the common registers of the tsensor controller.
|
||||
* @irq: irq number of the tsensor controller.
|
||||
* @soc: id of the SOC type.
|
||||
* @irq_work: pointer to the irq work structure.
|
||||
* @lock: lock to implement synchronization.
|
||||
* @clk: pointer to the clock structure.
|
||||
* @clk_sec: pointer to the clock structure for accessing the base_second.
|
||||
* @sclk: pointer to the clock structure for accessing the tmu special clk.
|
||||
* @temp_error1: fused value of the first point trim.
|
||||
* @temp_error2: fused value of the second point trim.
|
||||
* @regulator: pointer to the tsensor regulator structure.
|
||||
* @reg_conf: pointer to structure to register with core thermal.
|
||||
* @ntrip: number of supported trip points.
|
||||
* @tmu_initialize: SoC specific tsensor initialization method
|
||||
* @tmu_control: SoC specific tsensor control method
|
||||
* @tmu_read: SoC specific tsensor temperature read method
|
||||
* @tmu_set_emulation: SoC specific tsensor emulation setting method
|
||||
* @tmu_clear_irqs: SoC specific tsensor interrupts clearing method
|
||||
*/
|
||||
struct meson_tsensor_data {
|
||||
int id;
|
||||
struct meson_tsensor_platform_data *pdata;
|
||||
void __iomem *base_c;
|
||||
void __iomem *base_e;
|
||||
int irq;
|
||||
enum soc_type soc;
|
||||
struct work_struct irq_work;
|
||||
struct mutex lock;
|
||||
struct clk *clk;
|
||||
u32 trim_info;
|
||||
struct thermal_zone_device *tzd;
|
||||
unsigned int ntrip;
|
||||
int (*tsensor_initialize)(struct platform_device *pdev);
|
||||
void (*tsensor_control)(struct platform_device *pdev,
|
||||
bool on);
|
||||
int (*tsensor_read)(struct meson_tsensor_data *data);
|
||||
void (*tsensor_set_emulation)(struct meson_tsensor_data *data,
|
||||
int temp);
|
||||
void (*tsensor_clear_irqs)(struct meson_tsensor_data *data);
|
||||
void (*tsensor_update_irqs)(struct meson_tsensor_data *data);
|
||||
};
|
||||
|
||||
static void meson_report_trigger(struct meson_tsensor_data *p)
|
||||
{
|
||||
char data[10], *envp[] = { data, NULL };
|
||||
struct thermal_zone_device *tz = p->tzd;
|
||||
int temp;
|
||||
unsigned int i;
|
||||
|
||||
if (!tz) {
|
||||
pr_err("No thermal zone device defined\n");
|
||||
return;
|
||||
}
|
||||
|
||||
thermal_zone_device_update(tz, THERMAL_EVENT_UNSPECIFIED);
|
||||
|
||||
mutex_lock(&tz->lock);
|
||||
/* Find the level for which trip happened */
|
||||
for (i = 0; i < of_thermal_get_ntrips(tz); i++) {
|
||||
tz->ops->get_trip_temp(tz, i, &temp);
|
||||
if (tz->last_temperature < temp)
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(data, sizeof(data), "%u", i);
|
||||
kobject_uevent_env(&tz->device.kobj, KOBJ_CHANGE, envp);
|
||||
mutex_unlock(&tz->lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* tsensor treats temperature as a mapped temperature code.
|
||||
* The temperature is converted differently depending on the calibration type.
|
||||
*/
|
||||
static u32 temp_to_code(struct meson_tsensor_data *data, int temp)
|
||||
{
|
||||
struct meson_tsensor_platform_data *pdata = data->pdata;
|
||||
long long int sensor_code;
|
||||
u32 uefuse, reg_code;
|
||||
int cal_a, cal_b, cal_c, cal_d, cal_type;
|
||||
|
||||
uefuse = data->trim_info;
|
||||
uefuse = uefuse & 0xffff;
|
||||
|
||||
/* T = 727.8*(u_real+u_efuse/(1<<16)) - 274.7 */
|
||||
/* u_readl = (5.05*YOUT)/((1<<16)+ 4.05*YOUT) */
|
||||
/*u_readl = (T + 274.7) / 727.8 - u_efuse / (1 << 16)*/
|
||||
/*Yout = (u_readl / (5.05 - 4.05u_readl)) *(1 << 16)*/
|
||||
cal_type = pdata->cal_type;
|
||||
cal_a = pdata->cal_a;
|
||||
cal_b = pdata->cal_b;
|
||||
cal_c = pdata->cal_c;
|
||||
cal_d = pdata->cal_d;
|
||||
switch (cal_type) {
|
||||
case 0x1:
|
||||
if (uefuse & 0x8000) {
|
||||
sensor_code = (1 << 16) * (temp * 10 + cal_c) / cal_d
|
||||
+ (1 << 16) * (uefuse & 0x7fff) / (1 << 16);
|
||||
} else {
|
||||
sensor_code = (1 << 16) * (temp * 10 + cal_c) / cal_d
|
||||
- (1 << 16) * (uefuse & 0x7fff) / (1 << 16);
|
||||
}
|
||||
sensor_code = (sensor_code * 100 /
|
||||
(cal_b - cal_a * sensor_code / (1 << 16)));
|
||||
reg_code = (sensor_code >> 0x4) & R1P1_TS_TEMP_MASK;
|
||||
break;
|
||||
default:
|
||||
pr_info("Cal_type not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return reg_code;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate a temperature value from a temperature code.
|
||||
* The unit of the temperature is degree Celsius.
|
||||
*/
|
||||
static int code_to_temp(struct meson_tsensor_data *data, int temp_code)
|
||||
{
|
||||
struct meson_tsensor_platform_data *pdata = data->pdata;
|
||||
int temp, cal_type, cal_a, cal_b, cal_c, cal_d;
|
||||
long long int sensor_temp;
|
||||
u32 uefuse;
|
||||
|
||||
uefuse = data->trim_info;
|
||||
uefuse = uefuse & 0xffff;
|
||||
sensor_temp = temp_code;
|
||||
/* T = 727.8*(u_real+u_efuse/(1<<16)) - 274.7 */
|
||||
/* u_readl = (5.05*YOUT)/((1<<16)+ 4.05*YOUT) */
|
||||
cal_type = pdata->cal_type;
|
||||
cal_a = pdata->cal_a;
|
||||
cal_b = pdata->cal_b;
|
||||
cal_c = pdata->cal_c;
|
||||
cal_d = pdata->cal_d;
|
||||
switch (cal_type) {
|
||||
case 0x1:
|
||||
sensor_temp = (sensor_temp * cal_b) / 100 * (1 << 16)
|
||||
/ (1 * (1 << 16) + cal_a * sensor_temp / 100);
|
||||
if (uefuse & 0x8000) {
|
||||
sensor_temp = 1000 * ((sensor_temp - (uefuse
|
||||
& (0x7fff))) * cal_d / (1 << 16) - cal_c) / 10;
|
||||
} else {
|
||||
sensor_temp = 1000 * ((sensor_temp + uefuse)
|
||||
* cal_d / (1 << 16) - cal_c) / 10;
|
||||
}
|
||||
temp = sensor_temp;
|
||||
break;
|
||||
default:
|
||||
pr_info("Cal_type not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
static int meson_tsensor_initialize(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_tsensor_data *data = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
|
||||
if (of_thermal_get_ntrips(data->tzd) > data->ntrip) {
|
||||
dev_info(&pdev->dev,
|
||||
"More trip points than supported by this tsensor.\n");
|
||||
dev_info(&pdev->dev,
|
||||
"%d trip points should be configured in polling mode.\n",
|
||||
(of_thermal_get_ntrips(data->tzd) - data->ntrip));
|
||||
}
|
||||
mutex_lock(&data->lock);
|
||||
ret = data->tsensor_initialize(pdev);
|
||||
mutex_unlock(&data->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void meson_tsensor_control(struct platform_device *pdev, bool on)
|
||||
{
|
||||
struct meson_tsensor_data *data = platform_get_drvdata(pdev);
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
data->tsensor_control(pdev, on);
|
||||
mutex_unlock(&data->lock);
|
||||
}
|
||||
|
||||
static void r1p1_tsensor_control(struct platform_device *pdev, bool on)
|
||||
{
|
||||
struct meson_tsensor_data *data = platform_get_drvdata(pdev);
|
||||
struct thermal_zone_device *tz = data->tzd;
|
||||
unsigned int con;
|
||||
|
||||
con = readl(data->base_c + R1P1_TS_CFG_REG1);
|
||||
|
||||
if (on) {
|
||||
con |= (of_thermal_is_trip_valid(tz, 0)
|
||||
<< R1P1_TS_IRQ_RISE0_EN_SHIT);
|
||||
con |= (0x1 << R1P1_TS_IRQ_LOGIC_EN_SHIT);
|
||||
con |= (R1P1_TS_FILTER_EN | R1P1_TS_VCM_EN | R1P1_TS_VBG_EN
|
||||
| R1P1_TS_DEM_EN | R1P1_TS_CH_SEL);
|
||||
clk_enable(data->clk);
|
||||
} else {
|
||||
clk_disable(data->clk);
|
||||
con &= ~((1 << R1P1_TS_IRQ_LOGIC_EN_SHIT)
|
||||
| (R1P1_TS_IRQ_ALL_CLR));
|
||||
con &= ~(R1P1_TS_FILTER_EN | R1P1_TS_VCM_EN | R1P1_TS_VBG_EN
|
||||
| R1P1_TS_IPTAT_EN | R1P1_TS_DEM_EN);
|
||||
}
|
||||
writel(con, data->base_c + R1P1_TS_CFG_REG1);
|
||||
}
|
||||
|
||||
|
||||
static int r1p1_tsensor_initialize(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_tsensor_data *data = platform_get_drvdata(pdev);
|
||||
struct meson_tsensor_platform_data *pdata = data->pdata;
|
||||
struct thermal_zone_device *tz = data->tzd;
|
||||
u32 trim_info = 0;
|
||||
u32 rising_threshold = 0, falling_threshold = 0;
|
||||
u32 reboot_reg = 0xffff, con = 0;
|
||||
int ret = 0, threshold_code, i;
|
||||
int temp, temp_hist, reboot_temp;
|
||||
unsigned int reg_off, bit_off;
|
||||
int ver;
|
||||
|
||||
/*frist get the r1p1 trim info*/
|
||||
trim_info = readl(data->base_e + R1P1_TRIM_INFO);
|
||||
pr_info("tsensor trim info: 0x%x!\n", trim_info);
|
||||
ver = (trim_info >> 24) & 0xff;
|
||||
/*r1p1 tsensor ver to doing*/
|
||||
if (((ver & 0xf) >> 2) == 0) {
|
||||
ret = ERANGE;
|
||||
pr_info("thermal calibration type not support: 0x%x!\n", ver);
|
||||
goto out;
|
||||
}
|
||||
if ((ver & 0x80) == 0) {
|
||||
ret = ERANGE;
|
||||
pr_info("thermal calibration data not valid: 0x%x!\n", ver);
|
||||
goto out;
|
||||
}
|
||||
data->trim_info = trim_info;
|
||||
|
||||
/*r1p1 init the ts reboot soc function*/
|
||||
reboot_temp = pdata->reboot_temp;
|
||||
reboot_reg = temp_to_code(data, reboot_temp / MCELSIUS);
|
||||
con = (readl(data->base_c + R1P1_TS_CFG_REG2) | (reboot_reg << 4));
|
||||
con |= (R1P1_TS_HITEMP_EN | R1P1_TS_REBOOT_ALL_EN);
|
||||
con |= (R1P1_TS_REBOOT_TIME);
|
||||
pr_info("tsensor hireboot: 0x%x\n", con);
|
||||
writel(con, data->base_c + R1P1_TS_CFG_REG2);
|
||||
/*
|
||||
* Write temperature code for rising and falling threshold
|
||||
* On r1p1 tsensor there are 4 rising and 4 falling threshold
|
||||
* registers (0x50-0x5c and 0x60-0x6c respectively). Each
|
||||
* register holds the value of two threshold levels (at bit
|
||||
* offsets 0 and 16). Based on the fact that there are atmost
|
||||
* eight possible trigger levels, calculate the register and
|
||||
* bit offsets where the threshold levels are to be written.
|
||||
*
|
||||
* e.g.
|
||||
* R1P1_TS_CFG_REG4 (0x4 << 2)
|
||||
* [23:12] - rise_th0
|
||||
* [11:0] - rise_th1
|
||||
* R1P1_TS_CFG_REG5 (0x5 << 2)
|
||||
* [23:12] - rise_th2
|
||||
* [11:0] - rise_th3
|
||||
* R1P1_TS_CFG_REG6 (0x6 << 2)
|
||||
* [23:12] - fall_th0
|
||||
* [11:0] - fall_th1
|
||||
* R1P1_TS_CFG_REG7 (0x7 << 2)
|
||||
* [23:12] - fall_th2
|
||||
* [11:0] - fall_th3
|
||||
*/
|
||||
for (i = (of_thermal_get_ntrips(tz) - 1); i >= 0; i--) {
|
||||
reg_off = (i / 2) << 2;
|
||||
bit_off = ((i + 1) % 2);
|
||||
tz->ops->get_trip_temp(tz, i, &temp);
|
||||
temp /= MCELSIUS;
|
||||
tz->ops->get_trip_hyst(tz, i, &temp_hist);
|
||||
temp_hist = temp - (temp_hist / MCELSIUS);
|
||||
|
||||
/* Set 12-bit temperature code for rising threshold levels */
|
||||
threshold_code = temp_to_code(data, temp);
|
||||
rising_threshold = readl(data->base_c +
|
||||
R1P1_TS_CFG_REG4 + reg_off);
|
||||
rising_threshold &= ~(R1P1_TS_TEMP_MASK << (12 * bit_off));
|
||||
rising_threshold |= threshold_code << (12 * bit_off);
|
||||
writel(rising_threshold,
|
||||
data->base_c + R1P1_TS_CFG_REG4 + reg_off);
|
||||
|
||||
/* Set 12-bit temperature code for falling threshold levels */
|
||||
threshold_code = temp_to_code(data, temp_hist);
|
||||
falling_threshold = readl(data->base_c +
|
||||
R1P1_TS_CFG_REG6 + reg_off);
|
||||
falling_threshold &= ~(R1P1_TS_TEMP_MASK << (12 * bit_off));
|
||||
falling_threshold |= threshold_code << (12 * bit_off);
|
||||
writel(falling_threshold,
|
||||
data->base_c + R1P1_TS_CFG_REG6 + reg_off);
|
||||
}
|
||||
data->tsensor_clear_irqs(data);
|
||||
out:
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
static int r1p1_tsensor_read(struct meson_tsensor_data *data)
|
||||
{
|
||||
int j, cnt = 0;
|
||||
unsigned int tvalue = 0;
|
||||
unsigned int value_all = 0;
|
||||
|
||||
/*
|
||||
*r1p1 tsensor store 16 temp value.
|
||||
*read d0-d15 and get the average temp.
|
||||
*/
|
||||
for (j = 0; j < R1P1_TS_VALUE_CONT; j++) {
|
||||
tvalue = readl(data->base_c + R1P1_TS_STAT0);
|
||||
tvalue = tvalue & 0xffff;
|
||||
if ((tvalue >= 0x18a9) && (tvalue <= 0x32a6)) {
|
||||
cnt++;
|
||||
value_all += (tvalue & 0xffff);
|
||||
}
|
||||
}
|
||||
tvalue = value_all / cnt;
|
||||
return tvalue;
|
||||
}
|
||||
|
||||
static void r1p1_tsensor_set_emulation(struct meson_tsensor_data *data,
|
||||
int temp)
|
||||
{
|
||||
pr_info("r1p1 ts no emulation\n");
|
||||
}
|
||||
|
||||
static void r1p1_tsensor_clear_irqs(struct meson_tsensor_data *data)
|
||||
{
|
||||
unsigned int val_irq;
|
||||
|
||||
val_irq = (readl(data->base_c + R1P1_TS_STAT1)
|
||||
& R1P1_TS_IRQ_MASK);
|
||||
val_irq = (readl(data->base_c + R1P1_TS_CFG_REG1)
|
||||
| (val_irq << R1P1_TS_IRQ_ALL_CLR_SHIT));
|
||||
/* clear the interrupts */
|
||||
writel(val_irq, data->base_c + R1P1_TS_CFG_REG1);
|
||||
/* restore clear enable*/
|
||||
val_irq = (readl(data->base_c + R1P1_TS_CFG_REG1)
|
||||
& (~R1P1_TS_IRQ_ALL_CLR));
|
||||
writel(val_irq, data->base_c + R1P1_TS_CFG_REG1);
|
||||
|
||||
}
|
||||
|
||||
static void r1p1_tsensor_update_irqs(struct meson_tsensor_data *data)
|
||||
{
|
||||
struct thermal_zone_device *tz = data->tzd;
|
||||
int temp;
|
||||
unsigned int i, con;
|
||||
|
||||
/* Find the level for which trip happened */
|
||||
for (i = 0; i < of_thermal_get_ntrips(tz); i++) {
|
||||
tz->ops->get_trip_temp(tz, i, &temp);
|
||||
if (tz->last_temperature < (temp - 1000))
|
||||
break;
|
||||
}
|
||||
|
||||
con = readl(data->base_c + R1P1_TS_CFG_REG1);
|
||||
con &= ~(R1P1_TS_IRQ_ALL_EN);
|
||||
con |= (of_thermal_is_trip_valid(tz, i)
|
||||
<< (R1P1_TS_IRQ_RISE0_EN_SHIT + i));
|
||||
con |= (of_thermal_is_trip_valid(tz, i - 1)
|
||||
<< (R1P1_TS_IRQ_FALL0_EN_SHIT + i - 1));
|
||||
con &= ~(R1P1_TS_IRQ_ALL_CLR);
|
||||
writel(con, data->base_c + R1P1_TS_CFG_REG1);
|
||||
pr_debug("tsensor update irq: 0x%x, i: %d\n", con, i);
|
||||
}
|
||||
|
||||
static int meson_get_temp(void *p, int *temp)
|
||||
{
|
||||
struct meson_tsensor_data *data = p;
|
||||
|
||||
if (!data || !data->tsensor_read)
|
||||
return -EINVAL;
|
||||
mutex_lock(&data->lock);
|
||||
*temp = code_to_temp(data, data->tsensor_read(data));
|
||||
mutex_unlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void meson_tsensor_work(struct work_struct *work)
|
||||
{
|
||||
|
||||
struct meson_tsensor_data *data = container_of(work,
|
||||
struct meson_tsensor_data, irq_work);
|
||||
|
||||
meson_report_trigger(data);
|
||||
mutex_lock(&data->lock);
|
||||
/* TODO: take action based on particular interrupt */
|
||||
data->tsensor_update_irqs(data);
|
||||
data->tsensor_clear_irqs(data);
|
||||
mutex_unlock(&data->lock);
|
||||
enable_irq(data->irq);
|
||||
|
||||
}
|
||||
|
||||
static irqreturn_t meson_tsensor_irq(int irq, void *id)
|
||||
{
|
||||
struct meson_tsensor_data *data = id;
|
||||
|
||||
disable_irq_nosync(irq);
|
||||
schedule_work(&data->irq_work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static const struct of_device_id meson_tsensor_match[] = {
|
||||
{ .compatible = "amlogic, r1p1-tsensor", },
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, meson_tsensor_match);
|
||||
|
||||
static int meson_of_get_soc_type(struct device_node *np)
|
||||
{
|
||||
if (of_device_is_compatible(np, "amlogic, r1p1-tsensor"))
|
||||
return SOC_ARCH_TS_R1P1;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int meson_of_sensor_conf(struct platform_device *pdev,
|
||||
struct meson_tsensor_platform_data *pdata)
|
||||
{
|
||||
if (of_property_read_u32(pdev->dev.of_node, "cal_type",
|
||||
&pdata->cal_type)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Missing cal_type using default %d\n",
|
||||
0x0);
|
||||
pdata->cal_type = 0x0;
|
||||
}
|
||||
if (of_property_read_u32(pdev->dev.of_node, "cal_a",
|
||||
&pdata->cal_a)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Missing cal_a using default %d\n",
|
||||
0x0);
|
||||
pdata->cal_a = 0x0;
|
||||
}
|
||||
if (of_property_read_u32(pdev->dev.of_node, "cal_b",
|
||||
&pdata->cal_b)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Missing ctldata using default %d\n",
|
||||
0x0);
|
||||
pdata->cal_b = 0x0;
|
||||
}
|
||||
if (of_property_read_u32(pdev->dev.of_node, "cal_c",
|
||||
&pdata->cal_c)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Missing cal_c using default %d\n",
|
||||
0x0);
|
||||
pdata->cal_c = 0x0;
|
||||
}
|
||||
if (of_property_read_u32(pdev->dev.of_node, "cal_d",
|
||||
&pdata->cal_d)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Missing cal_d using default %d\n",
|
||||
0x0);
|
||||
pdata->cal_d = 0x0;
|
||||
}
|
||||
if (of_property_read_u32(pdev->dev.of_node, "rtemp",
|
||||
&pdata->reboot_temp)) {
|
||||
dev_warn(&pdev->dev,
|
||||
"Missing rtemp using default %d\n",
|
||||
TS_DEF_RTEMP);
|
||||
pdata->ctl_data = TS_DEF_RTEMP;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_map_dt_data(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_tsensor_data *data = platform_get_drvdata(pdev);
|
||||
struct meson_tsensor_platform_data *pdata;
|
||||
struct resource res;
|
||||
|
||||
if (!data || !pdev->dev.of_node)
|
||||
return -ENODEV;
|
||||
data->id = of_alias_get_id(pdev->dev.of_node, "tsensor");
|
||||
pr_info("tsensor id: %d\n", data->id);
|
||||
if (data->id < 0)
|
||||
data->id = 0;
|
||||
|
||||
data->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
|
||||
if (data->irq <= 0) {
|
||||
dev_err(&pdev->dev, "failed to get IRQ\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (of_address_to_resource(pdev->dev.of_node, 0, &res)) {
|
||||
dev_err(&pdev->dev, "failed to get Resource 0\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data->base_c = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
|
||||
if (!data->base_c) {
|
||||
dev_err(&pdev->dev, "Failed to ioremap memory\n");
|
||||
return -EADDRNOTAVAIL;
|
||||
}
|
||||
|
||||
if (of_address_to_resource(pdev->dev.of_node, 1, &res)) {
|
||||
dev_err(&pdev->dev, "failed to get Resource 1\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
data->base_e = devm_ioremap(&pdev->dev, res.start, resource_size(&res));
|
||||
if (!data->base_e) {
|
||||
dev_err(&pdev->dev, "Failed to ioremap memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
pdata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct meson_tsensor_platform_data),
|
||||
GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
meson_of_sensor_conf(pdev, pdata);
|
||||
data->pdata = pdata;
|
||||
data->soc = meson_of_get_soc_type(pdev->dev.of_node);
|
||||
switch (data->soc) {
|
||||
case SOC_ARCH_TS_R1P1:
|
||||
data->tsensor_initialize = r1p1_tsensor_initialize;
|
||||
data->tsensor_control = r1p1_tsensor_control;
|
||||
data->tsensor_read = r1p1_tsensor_read;
|
||||
data->tsensor_set_emulation = r1p1_tsensor_set_emulation;
|
||||
data->tsensor_clear_irqs = r1p1_tsensor_clear_irqs;
|
||||
data->tsensor_update_irqs = r1p1_tsensor_update_irqs;
|
||||
data->ntrip = 4;
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "Platform not supported\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct thermal_zone_of_device_ops meson_sensor_ops = {
|
||||
.get_temp = meson_get_temp,
|
||||
|
||||
};
|
||||
|
||||
static int meson_tsensor_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_tsensor_data *data;
|
||||
int ret;
|
||||
|
||||
pr_info("meson ts init\n");
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct meson_tsensor_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, data);
|
||||
mutex_init(&data->lock);
|
||||
|
||||
data->clk = devm_clk_get(&pdev->dev, "ts_comp");
|
||||
if (IS_ERR(data->clk)) {
|
||||
dev_err(&pdev->dev, "Failed to get tsclock\n");
|
||||
ret = PTR_ERR(data->clk);
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = clk_prepare(data->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to prepare tsclock\n");
|
||||
goto err_clk;
|
||||
}
|
||||
|
||||
ret = meson_map_dt_data(pdev);
|
||||
if (ret)
|
||||
goto err_clk;
|
||||
|
||||
INIT_WORK(&data->irq_work, meson_tsensor_work);
|
||||
|
||||
data->tzd = devm_thermal_zone_of_sensor_register(
|
||||
&pdev->dev, data->id, data, &meson_sensor_ops);
|
||||
if (IS_ERR(data->tzd)) {
|
||||
ret = PTR_ERR(data->tzd);
|
||||
dev_err(&pdev->dev, "Failed to register tsensor: %d\n", ret);
|
||||
goto err_thermal;
|
||||
}
|
||||
|
||||
ret = meson_tsensor_initialize(pdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to initialize tsensor\n");
|
||||
goto err_thermal;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, data->irq, meson_tsensor_irq,
|
||||
IRQF_TRIGGER_RISING | IRQF_SHARED, dev_name(&pdev->dev), data);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to request irq: %d\n", data->irq);
|
||||
goto err_thermal;
|
||||
}
|
||||
|
||||
meson_tsensor_control(pdev, true);
|
||||
return 0;
|
||||
|
||||
err_thermal:
|
||||
thermal_zone_of_sensor_unregister(&pdev->dev, data->tzd);
|
||||
err_clk:
|
||||
clk_unprepare(data->clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int meson_tsensor_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct meson_tsensor_data *data = platform_get_drvdata(pdev);
|
||||
struct thermal_zone_device *tzd = data->tzd;
|
||||
|
||||
thermal_zone_of_sensor_unregister(&pdev->dev, tzd);
|
||||
meson_tsensor_control(pdev, false);
|
||||
clk_unprepare(data->clk);
|
||||
devm_kfree(&pdev->dev, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int meson_tsensor_suspend(struct device *dev)
|
||||
{
|
||||
meson_tsensor_control(to_platform_device(dev), false);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int meson_tsensor_resume(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
|
||||
meson_tsensor_initialize(pdev);
|
||||
meson_tsensor_control(pdev, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(meson_tsensor_pm,
|
||||
meson_tsensor_suspend, meson_tsensor_resume);
|
||||
#define MESON_TSENSOR_PM (&meson_tsensor_pm)
|
||||
#else
|
||||
#define MESON_TSENSOR_PM NULL
|
||||
#endif
|
||||
|
||||
static struct platform_driver meson_tsensor_driver = {
|
||||
.driver = {
|
||||
.name = "meson-tsensor",
|
||||
.pm = MESON_TSENSOR_PM,
|
||||
.of_match_table = meson_tsensor_match,
|
||||
},
|
||||
.probe = meson_tsensor_probe,
|
||||
.remove = meson_tsensor_remove,
|
||||
};
|
||||
module_platform_driver(meson_tsensor_driver);
|
||||
|
||||
MODULE_DESCRIPTION("MESON Tsensor Driver");
|
||||
MODULE_AUTHOR("Huan Biao <huan.biao@amlogic.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:meson-tsensor");
|
||||
Reference in New Issue
Block a user