| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Author: zhanghongchen <zhanghongchen@loongson.cn> |
| * Yinbo Zhu <zhuyinbo@loongson.cn> |
| * Copyright (C) 2022-2023 Loongson Technology Corporation Limited |
| */ |
| |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/minmax.h> |
| #include <linux/module.h> |
| #include <linux/of_device.h> |
| #include <linux/platform_device.h> |
| #include <linux/thermal.h> |
| #include <linux/units.h> |
| #include "thermal_hwmon.h" |
| |
| #define LOONGSON2_MAX_SENSOR_SEL_NUM 3 |
| |
| #define LOONGSON2_THSENS_CTRL_HI_REG 0x0 |
| #define LOONGSON2_THSENS_CTRL_LOW_REG 0x8 |
| #define LOONGSON2_THSENS_STATUS_REG 0x10 |
| #define LOONGSON2_THSENS_OUT_REG 0x14 |
| |
| #define LOONGSON2_THSENS_INT_LO BIT(0) |
| #define LOONGSON2_THSENS_INT_HIGH BIT(1) |
| #define LOONGSON2_THSENS_OUT_MASK 0xFF |
| |
| struct loongson2_thermal_chip_data { |
| unsigned int thermal_sensor_sel; |
| }; |
| |
| struct loongson2_thermal_data { |
| void __iomem *regs; |
| const struct loongson2_thermal_chip_data *chip_data; |
| }; |
| |
| static int loongson2_thermal_set(struct loongson2_thermal_data *data, |
| int low, int high, bool enable) |
| { |
| u64 reg_ctrl = 0; |
| int reg_off = data->chip_data->thermal_sensor_sel * 2; |
| |
| low = clamp(-40, low, high); |
| high = clamp(125, low, high); |
| |
| low += HECTO; |
| high += HECTO; |
| |
| reg_ctrl = low; |
| reg_ctrl |= enable ? 0x100 : 0; |
| writew(reg_ctrl, data->regs + LOONGSON2_THSENS_CTRL_LOW_REG + reg_off); |
| |
| reg_ctrl = high; |
| reg_ctrl |= enable ? 0x100 : 0; |
| writew(reg_ctrl, data->regs + LOONGSON2_THSENS_CTRL_HI_REG + reg_off); |
| |
| return 0; |
| } |
| |
| static int loongson2_thermal_get_temp(struct thermal_zone_device *tz, int *temp) |
| { |
| u32 reg_val; |
| struct loongson2_thermal_data *data = thermal_zone_device_priv(tz); |
| |
| reg_val = readl(data->regs + LOONGSON2_THSENS_OUT_REG); |
| *temp = ((reg_val & LOONGSON2_THSENS_OUT_MASK) - HECTO) * KILO; |
| |
| return 0; |
| } |
| |
| static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev) |
| { |
| struct thermal_zone_device *tzd = dev; |
| struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd); |
| |
| writeb(LOONGSON2_THSENS_INT_LO | LOONGSON2_THSENS_INT_HIGH, data->regs + |
| LOONGSON2_THSENS_STATUS_REG); |
| |
| thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, int high) |
| { |
| struct loongson2_thermal_data *data = thermal_zone_device_priv(tz); |
| |
| return loongson2_thermal_set(data, low/MILLI, high/MILLI, true); |
| } |
| |
| static const struct thermal_zone_device_ops loongson2_of_thermal_ops = { |
| .get_temp = loongson2_thermal_get_temp, |
| .set_trips = loongson2_thermal_set_trips, |
| }; |
| |
| static int loongson2_thermal_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct loongson2_thermal_data *data; |
| struct thermal_zone_device *tzd; |
| int ret, irq, i; |
| |
| data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| data->chip_data = device_get_match_data(dev); |
| |
| data->regs = devm_platform_ioremap_resource(pdev, 0); |
| if (IS_ERR(data->regs)) |
| return PTR_ERR(data->regs); |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) |
| return irq; |
| |
| writeb(LOONGSON2_THSENS_INT_LO | LOONGSON2_THSENS_INT_HIGH, data->regs + |
| LOONGSON2_THSENS_STATUS_REG); |
| |
| loongson2_thermal_set(data, 0, 0, false); |
| |
| for (i = 0; i <= LOONGSON2_MAX_SENSOR_SEL_NUM; i++) { |
| tzd = devm_thermal_of_zone_register(dev, i, data, |
| &loongson2_of_thermal_ops); |
| |
| if (!IS_ERR(tzd)) |
| break; |
| |
| if (PTR_ERR(tzd) != ENODEV) |
| continue; |
| |
| return dev_err_probe(dev, PTR_ERR(tzd), "failed to register"); |
| } |
| |
| ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread, |
| IRQF_ONESHOT, "loongson2_thermal", tzd); |
| if (ret < 0) |
| return dev_err_probe(dev, ret, "failed to request alarm irq\n"); |
| |
| devm_thermal_add_hwmon_sysfs(dev, tzd); |
| |
| return 0; |
| } |
| |
| static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = { |
| .thermal_sensor_sel = 0, |
| }; |
| |
| static const struct of_device_id of_loongson2_thermal_match[] = { |
| { |
| .compatible = "loongson,ls2k1000-thermal", |
| .data = &loongson2_thermal_ls2k1000_data, |
| }, |
| { /* end */ } |
| }; |
| MODULE_DEVICE_TABLE(of, of_loongson2_thermal_match); |
| |
| static struct platform_driver loongson2_thermal_driver = { |
| .driver = { |
| .name = "loongson2_thermal", |
| .of_match_table = of_loongson2_thermal_match, |
| }, |
| .probe = loongson2_thermal_probe, |
| }; |
| module_platform_driver(loongson2_thermal_driver); |
| |
| MODULE_DESCRIPTION("Loongson2 thermal driver"); |
| MODULE_LICENSE("GPL"); |