|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */ | 
|  |  | 
|  | #include <linux/bits.h> | 
|  | #include <linux/err.h> | 
|  | #include <linux/hwmon.h> | 
|  | #include <linux/io.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/mod_devicetable.h> | 
|  | #include <linux/platform_device.h> | 
|  |  | 
|  | #define OFS_FAN_INST	0 /* Is 0 because plreg base will be set at INST */ | 
|  | #define OFS_FAN_FAIL	2 /* Is 2 bytes after base */ | 
|  | #define OFS_SEVSTAT	0 /* Is 0 because fn2 base will be set at SEVSTAT */ | 
|  | #define POWER_BIT	24 | 
|  |  | 
|  | struct gxp_fan_ctrl_drvdata { | 
|  | void __iomem	*base; | 
|  | void __iomem	*plreg; | 
|  | void __iomem	*fn2; | 
|  | }; | 
|  |  | 
|  | static bool fan_installed(struct device *dev, int fan) | 
|  | { | 
|  | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | 
|  | u8 val; | 
|  |  | 
|  | val = readb(drvdata->plreg + OFS_FAN_INST); | 
|  |  | 
|  | return !!(val & BIT(fan)); | 
|  | } | 
|  |  | 
|  | static long fan_failed(struct device *dev, int fan) | 
|  | { | 
|  | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | 
|  | u8 val; | 
|  |  | 
|  | val = readb(drvdata->plreg + OFS_FAN_FAIL); | 
|  |  | 
|  | return !!(val & BIT(fan)); | 
|  | } | 
|  |  | 
|  | static long fan_enabled(struct device *dev, int fan) | 
|  | { | 
|  | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | 
|  | u32 val; | 
|  |  | 
|  | /* | 
|  | * Check the power status as if the platform is off the value | 
|  | * reported for the PWM will be incorrect. Report fan as | 
|  | * disabled. | 
|  | */ | 
|  | val = readl(drvdata->fn2 + OFS_SEVSTAT); | 
|  |  | 
|  | return !!((val & BIT(POWER_BIT)) && fan_installed(dev, fan)); | 
|  | } | 
|  |  | 
|  | static int gxp_pwm_write(struct device *dev, u32 attr, int channel, long val) | 
|  | { | 
|  | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | 
|  |  | 
|  | switch (attr) { | 
|  | case hwmon_pwm_input: | 
|  | if (val > 255 || val < 0) | 
|  | return -EINVAL; | 
|  | writeb(val, drvdata->base + channel); | 
|  | return 0; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int gxp_fan_ctrl_write(struct device *dev, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel, long val) | 
|  | { | 
|  | switch (type) { | 
|  | case hwmon_pwm: | 
|  | return gxp_pwm_write(dev, attr, channel, val); | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int gxp_fan_read(struct device *dev, u32 attr, int channel, long *val) | 
|  | { | 
|  | switch (attr) { | 
|  | case hwmon_fan_enable: | 
|  | *val = fan_enabled(dev, channel); | 
|  | return 0; | 
|  | case hwmon_fan_fault: | 
|  | *val = fan_failed(dev, channel); | 
|  | return 0; | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int gxp_pwm_read(struct device *dev, u32 attr, int channel, long *val) | 
|  | { | 
|  | struct gxp_fan_ctrl_drvdata *drvdata = dev_get_drvdata(dev); | 
|  | u32 reg; | 
|  |  | 
|  | /* | 
|  | * Check the power status of the platform. If the platform is off | 
|  | * the value reported for the PWM will be incorrect. In this case | 
|  | * report a PWM of zero. | 
|  | */ | 
|  |  | 
|  | reg = readl(drvdata->fn2 + OFS_SEVSTAT); | 
|  |  | 
|  | if (reg & BIT(POWER_BIT)) | 
|  | *val = fan_installed(dev, channel) ? readb(drvdata->base + channel) : 0; | 
|  | else | 
|  | *val = 0; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int gxp_fan_ctrl_read(struct device *dev, enum hwmon_sensor_types type, | 
|  | u32 attr, int channel, long *val) | 
|  | { | 
|  | switch (type) { | 
|  | case hwmon_fan: | 
|  | return gxp_fan_read(dev, attr, channel, val); | 
|  | case hwmon_pwm: | 
|  | return gxp_pwm_read(dev, attr, channel, val); | 
|  | default: | 
|  | return -EOPNOTSUPP; | 
|  | } | 
|  | } | 
|  |  | 
|  | static umode_t gxp_fan_ctrl_is_visible(const void *_data, | 
|  | enum hwmon_sensor_types type, | 
|  | u32 attr, int channel) | 
|  | { | 
|  | umode_t mode = 0; | 
|  |  | 
|  | switch (type) { | 
|  | case hwmon_fan: | 
|  | switch (attr) { | 
|  | case hwmon_fan_enable: | 
|  | case hwmon_fan_fault: | 
|  | mode = 0444; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case hwmon_pwm: | 
|  | switch (attr) { | 
|  | case hwmon_pwm_input: | 
|  | mode = 0644; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | return mode; | 
|  | } | 
|  |  | 
|  | static const struct hwmon_ops gxp_fan_ctrl_ops = { | 
|  | .is_visible = gxp_fan_ctrl_is_visible, | 
|  | .read = gxp_fan_ctrl_read, | 
|  | .write = gxp_fan_ctrl_write, | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_channel_info * const gxp_fan_ctrl_info[] = { | 
|  | HWMON_CHANNEL_INFO(fan, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE, | 
|  | HWMON_F_FAULT | HWMON_F_ENABLE), | 
|  | HWMON_CHANNEL_INFO(pwm, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT, | 
|  | HWMON_PWM_INPUT), | 
|  | NULL | 
|  | }; | 
|  |  | 
|  | static const struct hwmon_chip_info gxp_fan_ctrl_chip_info = { | 
|  | .ops = &gxp_fan_ctrl_ops, | 
|  | .info = gxp_fan_ctrl_info, | 
|  |  | 
|  | }; | 
|  |  | 
|  | static int gxp_fan_ctrl_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct gxp_fan_ctrl_drvdata *drvdata; | 
|  | struct device *dev = &pdev->dev; | 
|  | struct device *hwmon_dev; | 
|  |  | 
|  | drvdata = devm_kzalloc(dev, sizeof(struct gxp_fan_ctrl_drvdata), | 
|  | GFP_KERNEL); | 
|  | if (!drvdata) | 
|  | return -ENOMEM; | 
|  |  | 
|  | drvdata->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); | 
|  | if (IS_ERR(drvdata->base)) | 
|  | return dev_err_probe(dev, PTR_ERR(drvdata->base), | 
|  | "failed to map base\n"); | 
|  |  | 
|  | drvdata->plreg = devm_platform_ioremap_resource_byname(pdev, | 
|  | "pl"); | 
|  | if (IS_ERR(drvdata->plreg)) | 
|  | return dev_err_probe(dev, PTR_ERR(drvdata->plreg), | 
|  | "failed to map plreg\n"); | 
|  |  | 
|  | drvdata->fn2 = devm_platform_ioremap_resource_byname(pdev, | 
|  | "fn2"); | 
|  | if (IS_ERR(drvdata->fn2)) | 
|  | return dev_err_probe(dev, PTR_ERR(drvdata->fn2), | 
|  | "failed to map fn2\n"); | 
|  |  | 
|  | hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, | 
|  | "hpe_gxp_fan_ctrl", | 
|  | drvdata, | 
|  | &gxp_fan_ctrl_chip_info, | 
|  | NULL); | 
|  |  | 
|  | return PTR_ERR_OR_ZERO(hwmon_dev); | 
|  | } | 
|  |  | 
|  | static const struct of_device_id gxp_fan_ctrl_of_match[] = { | 
|  | { .compatible = "hpe,gxp-fan-ctrl", }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, gxp_fan_ctrl_of_match); | 
|  |  | 
|  | static struct platform_driver gxp_fan_ctrl_driver = { | 
|  | .probe		= gxp_fan_ctrl_probe, | 
|  | .driver = { | 
|  | .name	= "gxp-fan-ctrl", | 
|  | .of_match_table = gxp_fan_ctrl_of_match, | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(gxp_fan_ctrl_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Nick Hawkins <nick.hawkins@hpe.com>"); | 
|  | MODULE_DESCRIPTION("HPE GXP fan controller"); | 
|  | MODULE_LICENSE("GPL"); |