1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Author: zhanghongchen <zhanghongchen@loongson.cn> 4 * Yinbo Zhu <zhuyinbo@loongson.cn> 5 * Copyright (C) 2022-2023 Loongson Technology Corporation Limited 6 */ 7 8 #include <linux/interrupt.h> 9 #include <linux/io.h> 10 #include <linux/minmax.h> 11 #include <linux/mod_devicetable.h> 12 #include <linux/module.h> 13 #include <linux/platform_device.h> 14 #include <linux/property.h> 15 #include <linux/thermal.h> 16 #include <linux/units.h> 17 18 #include "thermal_hwmon.h" 19 20 #define LOONGSON2_MAX_SENSOR_SEL_NUM 3 21 22 #define LOONGSON2_THSENS_CTRL_HI_REG 0x0 23 #define LOONGSON2_THSENS_CTRL_LOW_REG 0x8 24 #define LOONGSON2_THSENS_STATUS_REG 0x10 25 #define LOONGSON2_THSENS_OUT_REG 0x14 26 27 #define LOONGSON2_THSENS_INT_LO BIT(0) 28 #define LOONGSON2_THSENS_INT_HIGH BIT(1) 29 #define LOONGSON2_THSENS_INT_EN (LOONGSON2_THSENS_INT_LO | \ 30 LOONGSON2_THSENS_INT_HIGH) 31 #define LOONGSON2_THSENS_OUT_MASK 0xFF 32 33 /* 34 * This flag is used to indicate the temperature reading 35 * method of the Loongson-2K2000 36 */ 37 #define LS2K2000_THSENS_OUT_FLAG BIT(0) 38 39 struct loongson2_thermal_chip_data { 40 unsigned int thermal_sensor_sel; 41 unsigned int flags; 42 }; 43 44 struct loongson2_thermal_data { 45 void __iomem *ctrl_reg; 46 void __iomem *temp_reg; 47 const struct loongson2_thermal_chip_data *chip_data; 48 }; 49 50 static void loongson2_set_ctrl_regs(struct loongson2_thermal_data *data, 51 int ctrl_data, bool low, bool enable) 52 { 53 int reg_ctrl = 0; 54 int reg_off = data->chip_data->thermal_sensor_sel * 2; 55 int ctrl_reg = low ? LOONGSON2_THSENS_CTRL_LOW_REG : LOONGSON2_THSENS_CTRL_HI_REG; 56 57 reg_ctrl = ctrl_data + HECTO; 58 reg_ctrl |= enable ? 0x100 : 0; 59 writew(reg_ctrl, data->ctrl_reg + ctrl_reg + reg_off); 60 } 61 62 static int loongson2_thermal_set(struct loongson2_thermal_data *data, 63 int low, int high, bool enable) 64 { 65 /* Set low temperature threshold */ 66 loongson2_set_ctrl_regs(data, clamp(-40, low, high), true, enable); 67 68 /* Set high temperature threshold */ 69 loongson2_set_ctrl_regs(data, clamp(125, low, high), false, enable); 70 71 return 0; 72 } 73 74 static int loongson2_2k1000_get_temp(struct thermal_zone_device *tz, int *temp) 75 { 76 int val; 77 struct loongson2_thermal_data *data = thermal_zone_device_priv(tz); 78 79 val = readl(data->ctrl_reg + LOONGSON2_THSENS_OUT_REG); 80 *temp = ((val & LOONGSON2_THSENS_OUT_MASK) - HECTO) * KILO; 81 82 return 0; 83 } 84 85 static int loongson2_2k2000_get_temp(struct thermal_zone_device *tz, int *temp) 86 { 87 int val; 88 struct loongson2_thermal_data *data = thermal_zone_device_priv(tz); 89 90 val = readl(data->temp_reg); 91 *temp = ((val & 0xffff) * 820 / 0x4000 - 311) * KILO; 92 93 return 0; 94 } 95 96 static irqreturn_t loongson2_thermal_irq_thread(int irq, void *dev) 97 { 98 struct thermal_zone_device *tzd = dev; 99 struct loongson2_thermal_data *data = thermal_zone_device_priv(tzd); 100 101 writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG); 102 103 thermal_zone_device_update(tzd, THERMAL_EVENT_UNSPECIFIED); 104 105 return IRQ_HANDLED; 106 } 107 108 static int loongson2_thermal_set_trips(struct thermal_zone_device *tz, int low, int high) 109 { 110 struct loongson2_thermal_data *data = thermal_zone_device_priv(tz); 111 112 return loongson2_thermal_set(data, low/MILLI, high/MILLI, true); 113 } 114 115 static struct thermal_zone_device_ops loongson2_of_thermal_ops = { 116 .get_temp = loongson2_2k1000_get_temp, 117 .set_trips = loongson2_thermal_set_trips, 118 }; 119 120 static int loongson2_thermal_probe(struct platform_device *pdev) 121 { 122 struct device *dev = &pdev->dev; 123 struct loongson2_thermal_data *data; 124 struct thermal_zone_device *tzd; 125 int ret, irq, i; 126 127 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 128 if (!data) 129 return -ENOMEM; 130 131 data->chip_data = device_get_match_data(dev); 132 133 data->ctrl_reg = devm_platform_ioremap_resource(pdev, 0); 134 if (IS_ERR(data->ctrl_reg)) 135 return PTR_ERR(data->ctrl_reg); 136 137 /* The temperature output register is separate for Loongson-2K2000 */ 138 if (data->chip_data->flags & LS2K2000_THSENS_OUT_FLAG) { 139 data->temp_reg = devm_platform_ioremap_resource(pdev, 1); 140 if (IS_ERR(data->temp_reg)) 141 return PTR_ERR(data->temp_reg); 142 143 loongson2_of_thermal_ops.get_temp = loongson2_2k2000_get_temp; 144 } 145 146 irq = platform_get_irq(pdev, 0); 147 if (irq < 0) 148 return irq; 149 150 writeb(LOONGSON2_THSENS_INT_EN, data->ctrl_reg + LOONGSON2_THSENS_STATUS_REG); 151 152 loongson2_thermal_set(data, 0, 0, false); 153 154 for (i = 0; i <= LOONGSON2_MAX_SENSOR_SEL_NUM; i++) { 155 tzd = devm_thermal_of_zone_register(dev, i, data, 156 &loongson2_of_thermal_ops); 157 158 if (!IS_ERR(tzd)) 159 break; 160 161 if (PTR_ERR(tzd) != -ENODEV) 162 continue; 163 164 return dev_err_probe(dev, PTR_ERR(tzd), "failed to register"); 165 } 166 167 ret = devm_request_threaded_irq(dev, irq, NULL, loongson2_thermal_irq_thread, 168 IRQF_ONESHOT, "loongson2_thermal", tzd); 169 if (ret < 0) 170 return dev_err_probe(dev, ret, "failed to request alarm irq\n"); 171 172 devm_thermal_add_hwmon_sysfs(dev, tzd); 173 174 return 0; 175 } 176 177 static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k1000_data = { 178 .thermal_sensor_sel = 0, 179 .flags = 0, 180 }; 181 182 static const struct loongson2_thermal_chip_data loongson2_thermal_ls2k2000_data = { 183 .thermal_sensor_sel = 0, 184 .flags = LS2K2000_THSENS_OUT_FLAG, 185 }; 186 187 static const struct of_device_id of_loongson2_thermal_match[] = { 188 { 189 .compatible = "loongson,ls2k1000-thermal", 190 .data = &loongson2_thermal_ls2k1000_data, 191 }, 192 { 193 .compatible = "loongson,ls2k2000-thermal", 194 .data = &loongson2_thermal_ls2k2000_data, 195 }, 196 { /* end */ } 197 }; 198 MODULE_DEVICE_TABLE(of, of_loongson2_thermal_match); 199 200 static struct platform_driver loongson2_thermal_driver = { 201 .driver = { 202 .name = "loongson2_thermal", 203 .of_match_table = of_loongson2_thermal_match, 204 }, 205 .probe = loongson2_thermal_probe, 206 }; 207 module_platform_driver(loongson2_thermal_driver); 208 209 MODULE_DESCRIPTION("Loongson2 thermal driver"); 210 MODULE_AUTHOR("Loongson Technology Corporation Limited"); 211 MODULE_LICENSE("GPL"); 212