/* * Copyright (c) 2016, Theobroma Systems Design und Consulting GmbH * Dr. Philipp Tomsich * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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 #include #include /** * struct sun9i_thermal_sensor - hold the information of thermal sensor * @thermal: pointer to the platform/configuration data * @tzd: pointer to a thermal zone * @id: identifier of the thermal sensor */ struct sun9i_thermal_sensor { struct sun9i_thermal_data *thermal; struct thermal_zone_device *tzd; const char* name; int id; }; /** * struct sun9i_thermal_data - hold the private data of thermal driver * @pdev: platform device of thermal * @reset: the reset controller of tsadc * @sensors[SOC_MAX_SENSORS]: the thermal sensor * @mclk: the controller clock (GPADC) is divided down from the exteral 24MHz * @hclk: the AHB bus clock (for register accesses) * @grf: the general register file will be used to do static set by software * @regs: the base address of tsadc controller */ struct sun9i_thermal_data { struct platform_device *pdev; struct reset_control *reset; struct sun9i_thermal_sensor *sensors; u32 num_channels; struct clk *hclk; struct clk *mclk; void __iomem *regs; /* * Conversion parameters from the device-tree for the following * conversion function: * tCELSIUS = ((MULTIPLIER * data) + OFFSET) / DIVISOR * * These should be configured through the DTS keys: * allwinner,sun9i-tsc-multiplier * allwinner,sun9i-tsc-offset * allwinner,sun9i-tsc-divisor */ s32 multiplier; s32 offset; s32 divisor; }; static const struct of_device_id of_sun9i_thermal_match[] = { { .compatible = "allwinner,sun9i-tsc", }, { /* end */ }, }; MODULE_DEVICE_TABLE(of, of_sun9i_thermal_match); #if 0 static void rockchip_thermal_toggle_sensor(struct rockchip_thermal_sensor *sensor, bool on) { struct thermal_zone_device *tzd = sensor->tzd; tzd->ops->set_mode(tzd, on ? THERMAL_DEVICE_ENABLED : THERMAL_DEVICE_DISABLED); } static irqreturn_t rockchip_thermal_alarm_irq_thread(int irq, void *dev) { struct rockchip_thermal_data *thermal = dev; int i; dev_dbg(&thermal->pdev->dev, "thermal alarm\n"); thermal->chip->irq_ack(thermal->regs); for (i = 0; i < thermal->chip->chn_num; i++) thermal_zone_device_update(thermal->sensors[i].tzd); return IRQ_HANDLED; } #endif #define THSx_DATA(ch) (0x40 + (ch) * 0x4) static int sun9i_thermal_get_temp(void *_sensor, int *out_temp) { struct sun9i_thermal_sensor *sensor = _sensor; struct sun9i_thermal_data *thermal = sensor->thermal; s32 val; val = readl_relaxed(thermal->regs + THSx_DATA(sensor->id)); *out_temp = (thermal->multiplier * val + thermal->offset) / (thermal->divisor); dev_dbg(&thermal->pdev->dev, "sensor %d - temp %d (raw 0x%x)\n", sensor->id, *out_temp, val); return 0; } static const struct thermal_zone_of_device_ops sun9i_of_thermal_ops = { .get_temp = sun9i_thermal_get_temp, }; static int sun9i_configure_from_dt(struct device *dev, struct device_node *node, struct sun9i_thermal_data *thermal) { u32 num_channels; int i; if (of_property_read_u32(node, "allwinner,sun9i-tsc-channels", &num_channels)) { dev_err(dev, "missing sun9i-tsc-channels (number of channels) property\n"); return -EINVAL; } thermal->sensors = devm_kzalloc(dev, sizeof(struct sun9i_thermal_sensor) * num_channels, GFP_KERNEL); thermal->num_channels = num_channels; for (i = 0; i < num_channels; ++i) if (of_property_read_string_index(node, "allwinner,sun9i-tsc-name", i, &thermal->sensors[i].name)) { dev_err(dev, "missing allwinner,sun9i-tsc-name property\n"); return -EINVAL; } if (of_property_read_s32(node, "allwinner,sun9i-tsc-multiplier", &thermal->multiplier)) { dev_warn(dev, "missing sun9i-tsc-multiplier, assuming 1\n"); thermal->multiplier = 1; } if (of_property_read_s32(node, "allwinner,sun9i-tsc-offset", &thermal->offset)) { dev_warn(dev, "missing sun9i-tsc-offset, assuming 0\n"); thermal->offset = 0; } if (of_property_read_s32(node, "allwinner,sun9i-tsc-divisor", &thermal->divisor)) { dev_warn(dev, "missing sun9i-tsc-divisor, assuming 1\n"); thermal->divisor = 1; } return 0; } static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, char *buf) { struct sun9i_thermal_sensor *ts = dev_get_drvdata(dev); int temp; int error; error = sun9i_thermal_get_temp(ts, &temp); if (error) return error; return sprintf(buf, "%d\n", temp); } static ssize_t show_temp_label(struct device *dev, struct device_attribute *devattr, char *buf) { struct sun9i_thermal_sensor *ts = dev_get_drvdata(dev); return sprintf(buf, "SoC temperature (%s)\n", ts->name); } static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL); static DEVICE_ATTR(temp1_label, S_IRUGO, show_temp_label, NULL); static struct attribute *sun9i_ts_attrs[] = { &dev_attr_temp1_input.attr, &dev_attr_temp1_label.attr, NULL }; ATTRIBUTE_GROUPS(sun9i_ts); static int sun9i_thermal_register_sensor(struct platform_device *pdev, struct sun9i_thermal_data *thermal, int id) { int error; struct sun9i_thermal_sensor *sensor = &thermal->sensors[id]; struct device *hwmon; printk("sun9i_thermal_register_sensor entered for id %d\n", id); sensor->thermal = thermal; sensor->id = id; sensor->tzd = devm_thermal_zone_of_sensor_register(&pdev->dev, id, sensor, &sun9i_of_thermal_ops); if (IS_ERR(sensor->tzd)) { error = PTR_ERR(sensor->tzd); dev_err(&pdev->dev, "failed to register sensor %d: %d\n", id, error); return error; } /* * The thermal core does not register hwmon devices for DT-based * thermal zone sensors, such as this one. */ printk("registering %s with hwmon\n", sensor->name); hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, sensor->name, sensor, sun9i_ts_groups); if (IS_ERR(hwmon)) { dev_err(&pdev->dev, "failed to register sensor %s in hwmon: %ld\n", sensor->name, PTR_ERR(hwmon)); return PTR_ERR(hwmon); } return 0; } #if 1 /** * Reset TSADC Controller, reset all tsadc registers. */ static void sun9i_thermal_reset_controller(struct reset_control *reset) { reset_control_assert(reset); usleep_range(10, 20); reset_control_deassert(reset); } #endif static int sun9i_thermal_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct sun9i_thermal_data *thermal; const struct of_device_id *match; struct resource *res; int irq; int i; int error; match = of_match_node(of_sun9i_thermal_match, np); if (!match) return -ENXIO; irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "no irq resource?\n"); return -EINVAL; } thermal = devm_kzalloc(&pdev->dev, sizeof(struct sun9i_thermal_data), GFP_KERNEL); if (!thermal) return -ENOMEM; thermal->pdev = pdev; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); thermal->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(thermal->regs)) return PTR_ERR(thermal->regs); thermal->reset = devm_reset_control_get(&pdev->dev, NULL); if (IS_ERR(thermal->reset)) { error = PTR_ERR(thermal->reset); dev_err(&pdev->dev, "failed to get tsadc reset: %d\n", error); return error; } thermal->hclk = devm_clk_get(&pdev->dev, "ahb"); if (IS_ERR(thermal->hclk)) { error = PTR_ERR(thermal->hclk); dev_err(&pdev->dev, "failed to get sun9i_tsc AHB clock: %d\n", error); return error; } thermal->mclk = devm_clk_get(&pdev->dev, "mod"); if (IS_ERR(thermal->mclk)) { error = PTR_ERR(thermal->mclk); dev_err(&pdev->dev, "failed to get sun9i_tsc module clock: %d\n", error); return error; } error = clk_prepare_enable(thermal->hclk); if (error) { dev_err(&pdev->dev, "failed to enable pclk: %d\n", error); return error; } /* Ensure that our parent clock is slow enough */ error = sun9i_configure_from_dt(&pdev->dev, np, thermal); if (error) { dev_err(&pdev->dev, "failed to parse device tree data: %d\n", error); return error; } // clk_set_rate(thermal->mclk, 1000000); error = clk_prepare_enable(thermal->mclk); if (error) { dev_err(&pdev->dev, "failed to enable converter clock: %d\n", error); goto err_disable_hclk; } sun9i_thermal_reset_controller(thermal->reset); #if 0 thermal->chip->initialize(thermal->grf, thermal->regs, thermal->tshut_polarity); #endif for (i = 0; i < thermal->num_channels; i++) { error = sun9i_thermal_register_sensor(pdev, thermal, i); if (error) { dev_err(&pdev->dev, "failed to register sensor[%d] : error = %d\n", i, error); goto err_disable_mclk; } } #if 0 error = devm_request_threaded_irq(&pdev->dev, irq, NULL, &rockchip_thermal_alarm_irq_thread, IRQF_ONESHOT, "rockchip_thermal", thermal); if (error) { dev_err(&pdev->dev, "failed to request tsadc irq: %d\n", error); goto err_disable_pclk; } #endif // thermal->chip->control(thermal->regs, true); writel_relaxed(0x4000f, thermal->regs); #if 0 for (i = 0; i < thermal->chip->num_channels; i++) rockchip_thermal_toggle_sensor(&thermal->sensors[i], true); #endif platform_set_drvdata(pdev, thermal); return 0; err_disable_mclk: clk_disable_unprepare(thermal->mclk); err_disable_hclk: clk_disable_unprepare(thermal->hclk); return error; } static int sun9i_thermal_remove(struct platform_device *pdev) { #if 0 struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); int i; for (i = 0; i < thermal->chip->chn_num; i++) { struct rockchip_thermal_sensor *sensor = &thermal->sensors[i]; rockchip_thermal_toggle_sensor(sensor, false); } thermal->chip->control(thermal->regs, false); clk_disable_unprepare(thermal->pclk); clk_disable_unprepare(thermal->clk); #endif return 0; } static int __maybe_unused sun9i_thermal_suspend(struct device *dev) { #if 0 struct platform_device *pdev = to_platform_device(dev); struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); int i; for (i = 0; i < thermal->chip->chn_num; i++) rockchip_thermal_toggle_sensor(&thermal->sensors[i], false); thermal->chip->control(thermal->regs, false); clk_disable(thermal->pclk); clk_disable(thermal->clk); pinctrl_pm_select_sleep_state(dev); #endif return 0; } static int __maybe_unused sun9i_thermal_resume(struct device *dev) { #if 0 struct platform_device *pdev = to_platform_device(dev); struct rockchip_thermal_data *thermal = platform_get_drvdata(pdev); int i; int error; error = clk_enable(thermal->clk); if (error) return error; error = clk_enable(thermal->pclk); if (error) { clk_disable(thermal->clk); return error; } rockchip_thermal_reset_controller(thermal->reset); thermal->chip->initialize(thermal->grf, thermal->regs, thermal->tshut_polarity); for (i = 0; i < thermal->chip->chn_num; i++) { int id = thermal->sensors[i].id; thermal->chip->set_tshut_mode(id, thermal->regs, thermal->tshut_mode); thermal->chip->set_tshut_temp(thermal->chip->table, id, thermal->regs, thermal->tshut_temp); } thermal->chip->control(thermal->regs, true); for (i = 0; i < thermal->chip->chn_num; i++) rockchip_thermal_toggle_sensor(&thermal->sensors[i], true); pinctrl_pm_select_default_state(dev); #endif return 0; } static SIMPLE_DEV_PM_OPS(sun9i_thermal_pm_ops, sun9i_thermal_suspend, sun9i_thermal_resume); static struct platform_driver sun9i_thermal_driver = { .driver = { .name = "allwinner-sun9i-thermal", .pm = &sun9i_thermal_pm_ops, .of_match_table = of_sun9i_thermal_match, }, .probe = sun9i_thermal_probe, .remove = sun9i_thermal_remove, }; module_platform_driver(sun9i_thermal_driver); MODULE_DESCRIPTION("Allwinner A80 thermal sensor controller driver"); MODULE_AUTHOR("Theobroma Systems GmbH"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:allwinner-thermal");