|  | /* | 
|  | * ST Thermal Sensor Driver core routines | 
|  | * Author: Ajit Pal Singh <ajitpal.singh@st.com> | 
|  | * | 
|  | * Copyright (C) 2003-2014 STMicroelectronics (R&D) Limited | 
|  | * | 
|  | * 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. | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/clk.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/of.h> | 
|  | #include <linux/of_device.h> | 
|  |  | 
|  | #include "st_thermal.h" | 
|  |  | 
|  | /* The Thermal Framework expects millidegrees */ | 
|  | #define mcelsius(temp)			((temp) * 1000) | 
|  |  | 
|  | /* | 
|  | * Function to allocate regfields which are common | 
|  | * between syscfg and memory mapped based sensors | 
|  | */ | 
|  | int st_thermal_alloc_regfields(struct st_thermal_sensor *sensor) | 
|  | { | 
|  | struct device *dev = sensor->dev; | 
|  | struct regmap *regmap = sensor->regmap; | 
|  | const struct reg_field *reg_fields = sensor->cdata->reg_fields; | 
|  |  | 
|  | sensor->dcorrect = devm_regmap_field_alloc(dev, regmap, | 
|  | reg_fields[DCORRECT]); | 
|  |  | 
|  | sensor->overflow = devm_regmap_field_alloc(dev, regmap, | 
|  | reg_fields[OVERFLOW]); | 
|  |  | 
|  | sensor->temp_data = devm_regmap_field_alloc(dev, regmap, | 
|  | reg_fields[DATA]); | 
|  |  | 
|  | if (IS_ERR(sensor->dcorrect) || | 
|  | IS_ERR(sensor->overflow) || | 
|  | IS_ERR(sensor->temp_data)) { | 
|  | dev_err(dev, "failed to allocate common regfields\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return sensor->ops->alloc_regfields(sensor); | 
|  | } | 
|  |  | 
|  | static int st_thermal_sensor_on(struct st_thermal_sensor *sensor) | 
|  | { | 
|  | int ret; | 
|  | struct device *dev = sensor->dev; | 
|  |  | 
|  | ret = clk_prepare_enable(sensor->clk); | 
|  | if (ret) { | 
|  | dev_err(dev, "failed to enable clk\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = sensor->ops->power_ctrl(sensor, POWER_ON); | 
|  | if (ret) { | 
|  | dev_err(dev, "failed to power on sensor\n"); | 
|  | clk_disable_unprepare(sensor->clk); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int st_thermal_sensor_off(struct st_thermal_sensor *sensor) | 
|  | { | 
|  | int ret; | 
|  |  | 
|  | ret = sensor->ops->power_ctrl(sensor, POWER_OFF); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | clk_disable_unprepare(sensor->clk); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int st_thermal_calibration(struct st_thermal_sensor *sensor) | 
|  | { | 
|  | int ret; | 
|  | unsigned int val; | 
|  | struct device *dev = sensor->dev; | 
|  |  | 
|  | /* Check if sensor calibration data is already written */ | 
|  | ret = regmap_field_read(sensor->dcorrect, &val); | 
|  | if (ret) { | 
|  | dev_err(dev, "failed to read calibration data\n"); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | if (!val) { | 
|  | /* | 
|  | * Sensor calibration value not set by bootloader, | 
|  | * default calibration data to be used | 
|  | */ | 
|  | ret = regmap_field_write(sensor->dcorrect, | 
|  | sensor->cdata->calibration_val); | 
|  | if (ret) | 
|  | dev_err(dev, "failed to set calibration data\n"); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* Callback to get temperature from HW*/ | 
|  | static int st_thermal_get_temp(struct thermal_zone_device *th, | 
|  | unsigned long *temperature) | 
|  | { | 
|  | struct st_thermal_sensor *sensor = th->devdata; | 
|  | struct device *dev = sensor->dev; | 
|  | unsigned int temp; | 
|  | unsigned int overflow; | 
|  | int ret; | 
|  |  | 
|  | ret = regmap_field_read(sensor->overflow, &overflow); | 
|  | if (ret) | 
|  | return ret; | 
|  | if (overflow) | 
|  | return -EIO; | 
|  |  | 
|  | ret = regmap_field_read(sensor->temp_data, &temp); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | temp += sensor->cdata->temp_adjust_val; | 
|  | temp = mcelsius(temp); | 
|  |  | 
|  | dev_dbg(dev, "temperature: %d\n", temp); | 
|  |  | 
|  | *temperature = temp; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int st_thermal_get_trip_type(struct thermal_zone_device *th, | 
|  | int trip, enum thermal_trip_type *type) | 
|  | { | 
|  | struct st_thermal_sensor *sensor = th->devdata; | 
|  | struct device *dev = sensor->dev; | 
|  |  | 
|  | switch (trip) { | 
|  | case 0: | 
|  | *type = THERMAL_TRIP_CRITICAL; | 
|  | break; | 
|  | default: | 
|  | dev_err(dev, "invalid trip point\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int st_thermal_get_trip_temp(struct thermal_zone_device *th, | 
|  | int trip, unsigned long *temp) | 
|  | { | 
|  | struct st_thermal_sensor *sensor = th->devdata; | 
|  | struct device *dev = sensor->dev; | 
|  |  | 
|  | switch (trip) { | 
|  | case 0: | 
|  | *temp = mcelsius(sensor->cdata->crit_temp); | 
|  | break; | 
|  | default: | 
|  | dev_err(dev, "Invalid trip point\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static struct thermal_zone_device_ops st_tz_ops = { | 
|  | .get_temp	= st_thermal_get_temp, | 
|  | .get_trip_type	= st_thermal_get_trip_type, | 
|  | .get_trip_temp	= st_thermal_get_trip_temp, | 
|  | }; | 
|  |  | 
|  | int st_thermal_register(struct platform_device *pdev, | 
|  | const struct of_device_id *st_thermal_of_match) | 
|  | { | 
|  | struct st_thermal_sensor *sensor; | 
|  | struct device *dev = &pdev->dev; | 
|  | struct device_node *np = dev->of_node; | 
|  | const struct of_device_id *match; | 
|  |  | 
|  | int polling_delay; | 
|  | int ret; | 
|  |  | 
|  | if (!np) { | 
|  | dev_err(dev, "device tree node not found\n"); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL); | 
|  | if (!sensor) | 
|  | return -ENOMEM; | 
|  |  | 
|  | sensor->dev = dev; | 
|  |  | 
|  | match = of_match_device(st_thermal_of_match, dev); | 
|  | if (!(match && match->data)) | 
|  | return -EINVAL; | 
|  |  | 
|  | sensor->cdata = match->data; | 
|  | if (!sensor->cdata->ops) | 
|  | return -EINVAL; | 
|  |  | 
|  | sensor->ops = sensor->cdata->ops; | 
|  |  | 
|  | ret = sensor->ops->regmap_init(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = st_thermal_alloc_regfields(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | sensor->clk = devm_clk_get(dev, "thermal"); | 
|  | if (IS_ERR(sensor->clk)) { | 
|  | dev_err(dev, "failed to fetch clock\n"); | 
|  | return PTR_ERR(sensor->clk); | 
|  | } | 
|  |  | 
|  | if (sensor->ops->register_enable_irq) { | 
|  | ret = sensor->ops->register_enable_irq(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | ret = st_thermal_sensor_on(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = st_thermal_calibration(sensor); | 
|  | if (ret) | 
|  | goto sensor_off; | 
|  |  | 
|  | polling_delay = sensor->ops->register_enable_irq ? 0 : 1000; | 
|  |  | 
|  | sensor->thermal_dev = | 
|  | thermal_zone_device_register(dev_name(dev), 1, 0, sensor, | 
|  | &st_tz_ops, NULL, 0, polling_delay); | 
|  | if (IS_ERR(sensor->thermal_dev)) { | 
|  | dev_err(dev, "failed to register thermal zone device\n"); | 
|  | ret = PTR_ERR(sensor->thermal_dev); | 
|  | goto sensor_off; | 
|  | } | 
|  |  | 
|  | platform_set_drvdata(pdev, sensor); | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | sensor_off: | 
|  | st_thermal_sensor_off(sensor); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(st_thermal_register); | 
|  |  | 
|  | int st_thermal_unregister(struct platform_device *pdev) | 
|  | { | 
|  | struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); | 
|  |  | 
|  | st_thermal_sensor_off(sensor); | 
|  | thermal_zone_device_unregister(sensor->thermal_dev); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | EXPORT_SYMBOL_GPL(st_thermal_unregister); | 
|  |  | 
|  | #ifdef CONFIG_PM_SLEEP | 
|  | static int st_thermal_suspend(struct device *dev) | 
|  | { | 
|  | struct platform_device *pdev = to_platform_device(dev); | 
|  | struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); | 
|  |  | 
|  | return st_thermal_sensor_off(sensor); | 
|  | } | 
|  |  | 
|  | static int st_thermal_resume(struct device *dev) | 
|  | { | 
|  | int ret; | 
|  | struct platform_device *pdev = to_platform_device(dev); | 
|  | struct st_thermal_sensor *sensor = platform_get_drvdata(pdev); | 
|  |  | 
|  | ret = st_thermal_sensor_on(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | ret = st_thermal_calibration(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | if (sensor->ops->enable_irq) { | 
|  | ret = sensor->ops->enable_irq(sensor); | 
|  | if (ret) | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | SIMPLE_DEV_PM_OPS(st_thermal_pm_ops, st_thermal_suspend, st_thermal_resume); | 
|  | EXPORT_SYMBOL_GPL(st_thermal_pm_ops); | 
|  |  | 
|  | MODULE_AUTHOR("STMicroelectronics (R&D) Limited <ajitpal.singh@st.com>"); | 
|  | MODULE_DESCRIPTION("STMicroelectronics STi SoC Thermal Sensor Driver"); | 
|  | MODULE_LICENSE("GPL v2"); |