1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * sl67mcu hardware monitoring driver 4 * 5 * Copyright 2025 Kontron Europe GmbH 6 */ 7 8 #include <linux/bitfield.h> 9 #include <linux/hwmon.h> 10 #include <linux/kernel.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/regmap.h> 16 17 #define SA67MCU_VOLTAGE(n) (0x00 + ((n) * 2)) 18 #define SA67MCU_TEMP(n) (0x04 + ((n) * 2)) 19 20 struct sa67mcu_hwmon { 21 struct regmap *regmap; 22 u32 offset; 23 }; 24 25 static int sa67mcu_hwmon_read(struct device *dev, 26 enum hwmon_sensor_types type, u32 attr, 27 int channel, long *input) 28 { 29 struct sa67mcu_hwmon *hwmon = dev_get_drvdata(dev); 30 unsigned int offset; 31 u8 reg[2]; 32 int ret; 33 34 switch (type) { 35 case hwmon_in: 36 switch (attr) { 37 case hwmon_in_input: 38 offset = hwmon->offset + SA67MCU_VOLTAGE(channel); 39 break; 40 default: 41 return -EOPNOTSUPP; 42 } 43 break; 44 case hwmon_temp: 45 switch (attr) { 46 case hwmon_temp_input: 47 offset = hwmon->offset + SA67MCU_TEMP(channel); 48 break; 49 default: 50 return -EOPNOTSUPP; 51 } 52 break; 53 default: 54 return -EOPNOTSUPP; 55 } 56 57 /* Reading the low byte will capture the value */ 58 ret = regmap_bulk_read(hwmon->regmap, offset, reg, ARRAY_SIZE(reg)); 59 if (ret) 60 return ret; 61 62 *input = reg[1] << 8 | reg[0]; 63 64 /* Temperatures are s16 and in 0.1degC steps. */ 65 if (type == hwmon_temp) 66 *input = sign_extend32(*input, 15) * 100; 67 68 return 0; 69 } 70 71 static const struct hwmon_channel_info * const sa67mcu_hwmon_info[] = { 72 HWMON_CHANNEL_INFO(in, 73 HWMON_I_INPUT | HWMON_I_LABEL, 74 HWMON_I_INPUT | HWMON_I_LABEL), 75 HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), 76 NULL 77 }; 78 79 static const char *const sa67mcu_hwmon_in_labels[] = { 80 "VDDIN", 81 "VDD_RTC", 82 }; 83 84 static int sa67mcu_hwmon_read_string(struct device *dev, 85 enum hwmon_sensor_types type, u32 attr, 86 int channel, const char **str) 87 { 88 switch (type) { 89 case hwmon_in: 90 switch (attr) { 91 case hwmon_in_label: 92 *str = sa67mcu_hwmon_in_labels[channel]; 93 return 0; 94 default: 95 return -EOPNOTSUPP; 96 } 97 default: 98 return -EOPNOTSUPP; 99 } 100 } 101 102 static const struct hwmon_ops sa67mcu_hwmon_ops = { 103 .visible = 0444, 104 .read = sa67mcu_hwmon_read, 105 .read_string = sa67mcu_hwmon_read_string, 106 }; 107 108 static const struct hwmon_chip_info sa67mcu_hwmon_chip_info = { 109 .ops = &sa67mcu_hwmon_ops, 110 .info = sa67mcu_hwmon_info, 111 }; 112 113 static int sa67mcu_hwmon_probe(struct platform_device *pdev) 114 { 115 struct sa67mcu_hwmon *hwmon; 116 struct device *hwmon_dev; 117 int ret; 118 119 if (!pdev->dev.parent) 120 return -ENODEV; 121 122 hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); 123 if (!hwmon) 124 return -ENOMEM; 125 126 hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL); 127 if (!hwmon->regmap) 128 return -ENODEV; 129 130 ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset); 131 if (ret) 132 return -EINVAL; 133 134 hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, 135 "sa67mcu_hwmon", hwmon, 136 &sa67mcu_hwmon_chip_info, 137 NULL); 138 if (IS_ERR(hwmon_dev)) 139 dev_err(&pdev->dev, "failed to register as hwmon device"); 140 141 return PTR_ERR_OR_ZERO(hwmon_dev); 142 } 143 144 static const struct of_device_id sa67mcu_hwmon_of_match[] = { 145 { .compatible = "kontron,sa67mcu-hwmon", }, 146 {} 147 }; 148 MODULE_DEVICE_TABLE(of, sa67mcu_hwmon_of_match); 149 150 static struct platform_driver sa67mcu_hwmon_driver = { 151 .probe = sa67mcu_hwmon_probe, 152 .driver = { 153 .name = "sa67mcu-hwmon", 154 .of_match_table = sa67mcu_hwmon_of_match, 155 }, 156 }; 157 module_platform_driver(sa67mcu_hwmon_driver); 158 159 MODULE_DESCRIPTION("sa67mcu Hardware Monitoring Driver"); 160 MODULE_AUTHOR("Michael Walle <mwalle@kernel.org>"); 161 MODULE_LICENSE("GPL"); 162