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