diff --git a/MAINTAINERS b/MAINTAINERS index d1c0136d89a9..b56077f204dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14350,3 +14350,8 @@ AMLOGIC Irblaster driver M: yu.tu F: drivers/amlogic/irblaster/meson-irblaster.c F: drivers/amlogic/irblaster/meson-irblaster.h + +AMLOGIC THERMAL DRIVER +M: Huan Biao +F: drivers/amlgoic/thermal/meson_tsensor.c +F: drivers/amlogic/thermal/meson_cooldev.c diff --git a/drivers/amlogic/thermal/Makefile b/drivers/amlogic/thermal/Makefile index 1599773a5584..285d4feba769 100644 --- a/drivers/amlogic/thermal/Makefile +++ b/drivers/amlogic/thermal/Makefile @@ -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 diff --git a/drivers/amlogic/thermal/meson_cooldev.c b/drivers/amlogic/thermal/meson_cooldev.c new file mode 100644 index 000000000000..66c53834bb28 --- /dev/null +++ b/drivers/amlogic/thermal/meson_cooldev.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); diff --git a/drivers/amlogic/thermal/meson_tsensor.c b/drivers/amlogic/thermal/meson_tsensor.c new file mode 100644 index 000000000000..b343759fb22e --- /dev/null +++ b/drivers/amlogic/thermal/meson_tsensor.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:meson-tsensor");