diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwmon/Kconfig | 15 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/ab8500.c | 5 | ||||
-rw-r--r-- | drivers/hwmon/ads1015.c | 21 | ||||
-rw-r--r-- | drivers/hwmon/da9052-hwmon.c | 54 | ||||
-rw-r--r-- | drivers/hwmon/da9055-hwmon.c | 52 | ||||
-rw-r--r-- | drivers/hwmon/k10temp.c | 157 | ||||
-rw-r--r-- | drivers/hwmon/menf21bmc_hwmon.c | 230 | ||||
-rw-r--r-- | drivers/hwmon/ntc_thermistor.c | 25 | ||||
-rw-r--r-- | drivers/hwmon/smsc47b397.c | 51 | ||||
-rw-r--r-- | drivers/leds/Kconfig | 9 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/leds-menf21bmc.c | 131 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 15 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/menf21bmc.c | 132 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 10 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/menf21bmc_wdt.c | 203 |
19 files changed, 887 insertions, 227 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f00d048aa583..5286d7ce1f9e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -280,8 +280,8 @@ config SENSORS_K10TEMP If you say yes here you get support for the temperature sensor(s) inside your CPU. Supported are later revisions of the AMD Family 10h and all revisions of the AMD Family 11h, - 12h (Llano), 14h (Brazos), 15h (Bulldozer/Trinity/Kaveri) and - 16h (Kabini/Mullins) microarchitectures. + 12h (Llano), 14h (Brazos), 15h (Bulldozer/Trinity/Kaveri/Carrizo) + and 16h (Kabini/Mullins) microarchitectures. This driver can also be built as a module. If so, the module will be called k10temp. @@ -839,6 +839,16 @@ config SENSORS_MCP3021 This driver can also be built as a module. If so, the module will be called mcp3021. +config SENSORS_MENF21BMC_HWMON + tristate "MEN 14F021P00 BMC Hardware Monitoring" + depends on MFD_MENF21BMC + help + Say Y here to include support for the MEN 14F021P00 BMC + hardware monitoring. + + This driver can also be built as a module. If so the module + will be called menf21bmc_hwmon. + config SENSORS_ADCXX tristate "National Semiconductor ADCxxxSxxx" depends on SPI_MASTER @@ -1077,6 +1087,7 @@ config SENSORS_PC87427 config SENSORS_NTC_THERMISTOR tristate "NTC thermistor support from Murata" depends on !OF || IIO=n || IIO + depends on THERMAL || !THERMAL_OF help This driver supports NTC thermistors sensor reading and its interpretation. The driver can also monitor the temperature and diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index be28152c9848..c90a7611efaa 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -115,6 +115,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MAX6697) += max6697.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o +obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c index d844dc806853..8b6a4f4c8774 100644 --- a/drivers/hwmon/ab8500.c +++ b/drivers/hwmon/ab8500.c @@ -6,7 +6,7 @@ * * When the AB8500 thermal warning temperature is reached (threshold cannot * be changed by SW), an interrupt is set, and if no further action is taken - * within a certain time frame, pm_power off will be called. + * within a certain time frame, kernel_power_off will be called. * * When AB8500 thermal shutdown temperature is reached a hardware shutdown of * the AB8500 will occur. @@ -21,6 +21,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/power/ab8500.h> +#include <linux/reboot.h> #include <linux/slab.h> #include <linux/sysfs.h> #include "abx500.h" @@ -106,7 +107,7 @@ static void ab8500_thermal_power_off(struct work_struct *work) dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n"); - pm_power_off(); + kernel_power_off(); } static ssize_t ab8500_show_name(struct device *dev, diff --git a/drivers/hwmon/ads1015.c b/drivers/hwmon/ads1015.c index 126516414c11..f155b8380481 100644 --- a/drivers/hwmon/ads1015.c +++ b/drivers/hwmon/ads1015.c @@ -184,20 +184,18 @@ static int ads1015_get_channels_config_of(struct i2c_client *client) return -EINVAL; for_each_child_of_node(client->dev.of_node, node) { - const __be32 *property; - int len; + u32 pval; unsigned int channel; unsigned int pga = ADS1015_DEFAULT_PGA; unsigned int data_rate = ADS1015_DEFAULT_DATA_RATE; - property = of_get_property(node, "reg", &len); - if (!property || len != sizeof(int)) { + if (of_property_read_u32(node, "reg", &pval)) { dev_err(&client->dev, "invalid reg on %s\n", node->full_name); continue; } - channel = be32_to_cpup(property); + channel = pval; if (channel >= ADS1015_CHANNELS) { dev_err(&client->dev, "invalid channel index %d on %s\n", @@ -205,20 +203,17 @@ static int ads1015_get_channels_config_of(struct i2c_client *client) continue; } - property = of_get_property(node, "ti,gain", &len); - if (property && len == sizeof(int)) { - pga = be32_to_cpup(property); + if (!of_property_read_u32(node, "ti,gain", &pval)) { + pga = pval; if (pga > 6) { - dev_err(&client->dev, - "invalid gain on %s\n", + dev_err(&client->dev, "invalid gain on %s\n", node->full_name); return -EINVAL; } } - property = of_get_property(node, "ti,datarate", &len); - if (property && len == sizeof(int)) { - data_rate = be32_to_cpup(property); + if (!of_property_read_u32(node, "ti,datarate", &pval)) { + data_rate = pval; if (data_rate > 7) { dev_err(&client->dev, "invalid data_rate on %s\n", diff --git a/drivers/hwmon/da9052-hwmon.c b/drivers/hwmon/da9052-hwmon.c index d14ab3c45daa..692b3f34d88c 100644 --- a/drivers/hwmon/da9052-hwmon.c +++ b/drivers/hwmon/da9052-hwmon.c @@ -26,7 +26,6 @@ struct da9052_hwmon { struct da9052 *da9052; - struct device *class_device; struct mutex hwmon_lock; }; @@ -190,13 +189,6 @@ static ssize_t da9052_read_vbbat(struct device *dev, return sprintf(buf, "%d\n", vbbat_reg_to_mv(ret)); } -static ssize_t da9052_hwmon_show_name(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - return sprintf(buf, "da9052\n"); -} - static ssize_t show_label(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -243,10 +235,7 @@ static SENSOR_DEVICE_ATTR(temp8_input, S_IRUGO, da9052_read_tjunc, NULL, static SENSOR_DEVICE_ATTR(temp8_label, S_IRUGO, show_label, NULL, DA9052_ADC_TJUNC); -static DEVICE_ATTR(name, S_IRUGO, da9052_hwmon_show_name, NULL); - -static struct attribute *da9052_attr[] = { - &dev_attr_name.attr, +static struct attribute *da9052_attrs[] = { &sensor_dev_attr_in0_input.dev_attr.attr, &sensor_dev_attr_in0_label.dev_attr.attr, &sensor_dev_attr_in3_input.dev_attr.attr, @@ -268,54 +257,29 @@ static struct attribute *da9052_attr[] = { NULL }; -static const struct attribute_group da9052_attr_group = {.attrs = da9052_attr}; +ATTRIBUTE_GROUPS(da9052); static int da9052_hwmon_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct da9052_hwmon *hwmon; - int ret; + struct device *hwmon_dev; - hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da9052_hwmon), - GFP_KERNEL); + hwmon = devm_kzalloc(dev, sizeof(struct da9052_hwmon), GFP_KERNEL); if (!hwmon) return -ENOMEM; mutex_init(&hwmon->hwmon_lock); hwmon->da9052 = dev_get_drvdata(pdev->dev.parent); - platform_set_drvdata(pdev, hwmon); - - ret = sysfs_create_group(&pdev->dev.kobj, &da9052_attr_group); - if (ret) - goto err_mem; - - hwmon->class_device = hwmon_device_register(&pdev->dev); - if (IS_ERR(hwmon->class_device)) { - ret = PTR_ERR(hwmon->class_device); - goto err_sysfs; - } - - return 0; - -err_sysfs: - sysfs_remove_group(&pdev->dev.kobj, &da9052_attr_group); -err_mem: - return ret; -} - -static int da9052_hwmon_remove(struct platform_device *pdev) -{ - struct da9052_hwmon *hwmon = platform_get_drvdata(pdev); - - hwmon_device_unregister(hwmon->class_device); - sysfs_remove_group(&pdev->dev.kobj, &da9052_attr_group); - - return 0; + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9052", + hwmon, + da9052_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static struct platform_driver da9052_hwmon_driver = { .probe = da9052_hwmon_probe, - .remove = da9052_hwmon_remove, .driver = { .name = "da9052-hwmon", .owner = THIS_MODULE, diff --git a/drivers/hwmon/da9055-hwmon.c b/drivers/hwmon/da9055-hwmon.c index 35eb7738d711..9916a3fb4bb9 100644 --- a/drivers/hwmon/da9055-hwmon.c +++ b/drivers/hwmon/da9055-hwmon.c @@ -36,7 +36,6 @@ struct da9055_hwmon { struct da9055 *da9055; - struct device *class_device; struct mutex hwmon_lock; struct mutex irq_lock; struct completion done; @@ -200,13 +199,6 @@ static ssize_t da9055_read_tjunc(struct device *dev, + 3076332, 10000)); } -static ssize_t da9055_hwmon_show_name(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - return sprintf(buf, "da9055\n"); -} - static ssize_t show_label(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -236,10 +228,7 @@ static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, da9055_read_tjunc, NULL, static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, DA9055_ADC_TJUNC); -static DEVICE_ATTR(name, S_IRUGO, da9055_hwmon_show_name, NULL); - -static struct attribute *da9055_attr[] = { - &dev_attr_name.attr, +static struct attribute *da9055_attrs[] = { &sensor_dev_attr_in0_input.dev_attr.attr, &sensor_dev_attr_in0_label.dev_attr.attr, &sensor_dev_attr_in1_input.dev_attr.attr, @@ -254,15 +243,16 @@ static struct attribute *da9055_attr[] = { NULL }; -static const struct attribute_group da9055_attr_group = {.attrs = da9055_attr}; +ATTRIBUTE_GROUPS(da9055); static int da9055_hwmon_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct da9055_hwmon *hwmon; + struct device *hwmon_dev; int hwmon_irq, ret; - hwmon = devm_kzalloc(&pdev->dev, sizeof(struct da9055_hwmon), - GFP_KERNEL); + hwmon = devm_kzalloc(dev, sizeof(struct da9055_hwmon), GFP_KERNEL); if (!hwmon) return -ENOMEM; @@ -272,8 +262,6 @@ static int da9055_hwmon_probe(struct platform_device *pdev) init_completion(&hwmon->done); hwmon->da9055 = dev_get_drvdata(pdev->dev.parent); - platform_set_drvdata(pdev, hwmon); - hwmon_irq = platform_get_irq_byname(pdev, "HWMON"); if (hwmon_irq < 0) return hwmon_irq; @@ -288,36 +276,14 @@ static int da9055_hwmon_probe(struct platform_device *pdev) return ret; } - ret = sysfs_create_group(&pdev->dev.kobj, &da9055_attr_group); - if (ret) - return ret; - - hwmon->class_device = hwmon_device_register(&pdev->dev); - if (IS_ERR(hwmon->class_device)) { - ret = PTR_ERR(hwmon->class_device); - goto err; - } - - return 0; - -err: - sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group); - return ret; -} - -static int da9055_hwmon_remove(struct platform_device *pdev) -{ - struct da9055_hwmon *hwmon = platform_get_drvdata(pdev); - - sysfs_remove_group(&pdev->dev.kobj, &da9055_attr_group); - hwmon_device_unregister(hwmon->class_device); - - return 0; + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "da9055", + hwmon, + da9055_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static struct platform_driver da9055_hwmon_driver = { .probe = da9055_hwmon_probe, - .remove = da9055_hwmon_remove, .driver = { .name = "da9055-hwmon", .owner = THIS_MODULE, diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index f7b46f68ef43..1e7bdcdcb295 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -33,6 +33,9 @@ static bool force; module_param(force, bool, 0444); MODULE_PARM_DESC(force, "force loading on processors with erratum 319"); +/* Provide lock for writing to NB_SMU_IND_ADDR */ +static DEFINE_MUTEX(nb_smu_ind_mutex); + /* CPUID function 0x80000001, ebx */ #define CPUID_PKGTYPE_MASK 0xf0000000 #define CPUID_PKGTYPE_F 0x00000000 @@ -51,13 +54,38 @@ MODULE_PARM_DESC(force, "force loading on processors with erratum 319"); #define REG_NORTHBRIDGE_CAPABILITIES 0xe8 #define NB_CAP_HTC 0x00000400 +/* + * For F15h M60h, functionality of REG_REPORTED_TEMPERATURE + * has been moved to D0F0xBC_xD820_0CA4 [Reported Temperature + * Control] + */ +#define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4 +#define PCI_DEVICE_ID_AMD_15H_M60H_NB_F3 0x1573 + +static void amd_nb_smu_index_read(struct pci_dev *pdev, unsigned int devfn, + int offset, u32 *val) +{ + mutex_lock(&nb_smu_ind_mutex); + pci_bus_write_config_dword(pdev->bus, devfn, + 0xb8, offset); + pci_bus_read_config_dword(pdev->bus, devfn, + 0xbc, val); + mutex_unlock(&nb_smu_ind_mutex); +} + static ssize_t show_temp(struct device *dev, struct device_attribute *attr, char *buf) { u32 regval; - - pci_read_config_dword(to_pci_dev(dev), - REG_REPORTED_TEMPERATURE, ®val); + struct pci_dev *pdev = dev_get_drvdata(dev); + + if (boot_cpu_data.x86 == 0x15 && boot_cpu_data.x86_model == 0x60) { + amd_nb_smu_index_read(pdev, PCI_DEVFN(0, 0), + F15H_M60H_REPORTED_TEMP_CTRL_OFFSET, + ®val); + } else { + pci_read_config_dword(pdev, REG_REPORTED_TEMPERATURE, ®val); + } return sprintf(buf, "%u\n", (regval >> 21) * 125); } @@ -75,7 +103,7 @@ static ssize_t show_temp_crit(struct device *dev, u32 regval; int value; - pci_read_config_dword(to_pci_dev(dev), + pci_read_config_dword(dev_get_drvdata(dev), REG_HARDWARE_THERMAL_CONTROL, ®val); value = ((regval >> 16) & 0x7f) * 500 + 52000; if (show_hyst) @@ -83,17 +111,43 @@ static ssize_t show_temp_crit(struct device *dev, return sprintf(buf, "%d\n", value); } -static ssize_t show_name(struct device *dev, - struct device_attribute *attr, char *buf) -{ - return sprintf(buf, "k10temp\n"); -} - static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL); static DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL); static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); static SENSOR_DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit, NULL, 1); -static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static umode_t k10temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct pci_dev *pdev = dev_get_drvdata(dev); + + if (index >= 2) { + u32 reg_caps, reg_htc; + + pci_read_config_dword(pdev, REG_NORTHBRIDGE_CAPABILITIES, + ®_caps); + pci_read_config_dword(pdev, REG_HARDWARE_THERMAL_CONTROL, + ®_htc); + if (!(reg_caps & NB_CAP_HTC) || !(reg_htc & HTC_ENABLE)) + return 0; + } + return attr->mode; +} + +static struct attribute *k10temp_attrs[] = { + &dev_attr_temp1_input.attr, + &dev_attr_temp1_max.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_hyst.dev_attr.attr, + NULL +}; + +static const struct attribute_group k10temp_group = { + .attrs = k10temp_attrs, + .is_visible = k10temp_is_visible, +}; +__ATTRIBUTE_GROUPS(k10temp); static bool has_erratum_319(struct pci_dev *pdev) { @@ -132,76 +186,23 @@ static bool has_erratum_319(struct pci_dev *pdev) static int k10temp_probe(struct pci_dev *pdev, const struct pci_device_id *id) { - struct device *hwmon_dev; - u32 reg_caps, reg_htc; int unreliable = has_erratum_319(pdev); - int err; - - if (unreliable && !force) { - dev_err(&pdev->dev, - "unreliable CPU thermal sensor; monitoring disabled\n"); - err = -ENODEV; - goto exit; - } - - err = device_create_file(&pdev->dev, &dev_attr_temp1_input); - if (err) - goto exit; - err = device_create_file(&pdev->dev, &dev_attr_temp1_max); - if (err) - goto exit_remove; - - pci_read_config_dword(pdev, REG_NORTHBRIDGE_CAPABILITIES, ®_caps); - pci_read_config_dword(pdev, REG_HARDWARE_THERMAL_CONTROL, ®_htc); - if ((reg_caps & NB_CAP_HTC) && (reg_htc & HTC_ENABLE)) { - err = device_create_file(&pdev->dev, - &sensor_dev_attr_temp1_crit.dev_attr); - if (err) - goto exit_remove; - err = device_create_file(&pdev->dev, - &sensor_dev_attr_temp1_crit_hyst.dev_attr); - if (err) - goto exit_remove; - } - - err = device_create_file(&pdev->dev, &dev_attr_name); - if (err) - goto exit_remove; - - hwmon_dev = hwmon_device_register(&pdev->dev); - if (IS_ERR(hwmon_dev)) { - err = PTR_ERR(hwmon_dev); - goto exit_remove; - } - pci_set_drvdata(pdev, hwmon_dev); + struct device *dev = &pdev->dev; + struct device *hwmon_dev; - if (unreliable && force) - dev_warn(&pdev->dev, + if (unreliable) { + if (!force) { + dev_err(dev, + "unreliable CPU thermal sensor; monitoring disabled\n"); + return -ENODEV; + } + dev_warn(dev, "unreliable CPU thermal sensor; check erratum 319\n"); - return 0; - -exit_remove: - device_remove_file(&pdev->dev, &dev_attr_name); - device_remove_file(&pdev->dev, &dev_attr_temp1_input); - device_remove_file(&pdev->dev, &dev_attr_temp1_max); - device_remove_file(&pdev->dev, - &sensor_dev_attr_temp1_crit.dev_attr); - device_remove_file(&pdev->dev, - &sensor_dev_attr_temp1_crit_hyst.dev_attr); -exit: - return err; -} + } -static void k10temp_remove(struct pci_dev *pdev) -{ - hwmon_device_unregister(pci_get_drvdata(pdev)); - device_remove_file(&pdev->dev, &dev_attr_name); - device_remove_file(&pdev->dev, &dev_attr_temp1_input); - device_remove_file(&pdev->dev, &dev_attr_temp1_max); - device_remove_file(&pdev->dev, - &sensor_dev_attr_temp1_crit.dev_attr); - device_remove_file(&pdev->dev, - &sensor_dev_attr_temp1_crit_hyst.dev_attr); + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "k10temp", pdev, + k10temp_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static const struct pci_device_id k10temp_id_table[] = { @@ -211,6 +212,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_NB_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M10H_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M30H_NB_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_15H_M60H_NB_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_NB_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_16H_M30H_NB_F3) }, {} @@ -221,7 +223,6 @@ static struct pci_driver k10temp_driver = { .name = "k10temp", .id_table = k10temp_id_table, .probe = k10temp_probe, - .remove = k10temp_remove, }; module_pci_driver(k10temp_driver); diff --git a/drivers/hwmon/menf21bmc_hwmon.c b/drivers/hwmon/menf21bmc_hwmon.c new file mode 100644 index 000000000000..c92229d321c9 --- /dev/null +++ b/drivers/hwmon/menf21bmc_hwmon.c @@ -0,0 +1,230 @@ +/* + * MEN 14F021P00 Board Management Controller (BMC) hwmon driver. + * + * This is the core hwmon driver of the MEN 14F021P00 BMC. + * The BMC monitors the board voltages which can be access with this + * driver through sysfs. + * + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/i2c.h> + +#define DRV_NAME "menf21bmc_hwmon" + +#define BMC_VOLT_COUNT 5 +#define MENF21BMC_V33 0 +#define MENF21BMC_V5 1 +#define MENF21BMC_V12 2 +#define MENF21BMC_V5_SB 3 +#define MENF21BMC_VBAT 4 + +#define IDX_TO_VOLT_MIN_CMD(idx) (0x40 + idx) +#define IDX_TO_VOLT_MAX_CMD(idx) (0x50 + idx) +#define IDX_TO_VOLT_INP_CMD(idx) (0x60 + idx) + +struct menf21bmc_hwmon { + bool valid; + struct i2c_client *i2c_client; + unsigned long last_update; + int in_val[BMC_VOLT_COUNT]; + int in_min[BMC_VOLT_COUNT]; + int in_max[BMC_VOLT_COUNT]; +}; + +static const char *const input_names[] = { + [MENF21BMC_V33] = "MON_3_3V", + [MENF21BMC_V5] = "MON_5V", + [MENF21BMC_V12] = "MON_12V", + [MENF21BMC_V5_SB] = "5V_STANDBY", + [MENF21BMC_VBAT] = "VBAT" +}; + +static struct menf21bmc_hwmon *menf21bmc_hwmon_update(struct device *dev) +{ + int i; + int val; + struct menf21bmc_hwmon *drv_data = dev_get_drvdata(dev); + struct menf21bmc_hwmon *data_ret = drv_data; + + if (time_after(jiffies, drv_data->last_update + HZ) + || !drv_data->valid) { + for (i = 0; i < BMC_VOLT_COUNT; i++) { + val = i2c_smbus_read_word_data(drv_data->i2c_client, + IDX_TO_VOLT_INP_CMD(i)); + if (val < 0) { + data_ret = ERR_PTR(val); + goto abort; + } + drv_data->in_val[i] = val; + } + drv_data->last_update = jiffies; + drv_data->valid = true; + } +abort: + return data_ret; +} + +static int menf21bmc_hwmon_get_volt_limits(struct menf21bmc_hwmon *drv_data) +{ + int i, val; + + for (i = 0; i < BMC_VOLT_COUNT; i++) { + val = i2c_smbus_read_word_data(drv_data->i2c_client, + IDX_TO_VOLT_MIN_CMD(i)); + if (val < 0) + return val; + + drv_data->in_min[i] = val; + + val = i2c_smbus_read_word_data(drv_data->i2c_client, + IDX_TO_VOLT_MAX_CMD(i)); + if (val < 0) + return val; + + drv_data->in_max[i] = val; + } + return 0; +} + +static ssize_t +show_label(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%s\n", input_names[attr->index]); +} + +static ssize_t +show_in(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct menf21bmc_hwmon *drv_data = menf21bmc_hwmon_update(dev); + + if (IS_ERR(drv_data)) + return PTR_ERR(drv_data); + + return sprintf(buf, "%d\n", drv_data->in_val[attr->index]); +} + +static ssize_t +show_min(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct menf21bmc_hwmon *drv_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", drv_data->in_min[attr->index]); +} + +static ssize_t +show_max(struct device *dev, struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct menf21bmc_hwmon *drv_data = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", drv_data->in_max[attr->index]); +} + +#define create_voltage_sysfs(idx) \ +static SENSOR_DEVICE_ATTR(in##idx##_input, S_IRUGO, \ + show_in, NULL, idx); \ +static SENSOR_DEVICE_ATTR(in##idx##_min, S_IRUGO, \ + show_min, NULL, idx); \ +static SENSOR_DEVICE_ATTR(in##idx##_max, S_IRUGO, \ + show_max, NULL, idx); \ +static SENSOR_DEVICE_ATTR(in##idx##_label, S_IRUGO, \ + show_label, NULL, idx); + +create_voltage_sysfs(0); +create_voltage_sysfs(1); +create_voltage_sysfs(2); +create_voltage_sysfs(3); +create_voltage_sysfs(4); + +static struct attribute *menf21bmc_hwmon_attrs[] = { + &sensor_dev_attr_in0_input.dev_attr.attr, + &sensor_dev_attr_in0_min.dev_attr.attr, + &sensor_dev_attr_in0_max.dev_attr.attr, + &sensor_dev_attr_in0_label.dev_attr.attr, + + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_label.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_label.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_label.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_label.dev_attr.attr, + NULL +}; + +ATTRIBUTE_GROUPS(menf21bmc_hwmon); + +static int menf21bmc_hwmon_probe(struct platform_device *pdev) +{ + int ret; + struct menf21bmc_hwmon *drv_data; + struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent); + struct device *hwmon_dev; + + drv_data = devm_kzalloc(&pdev->dev, sizeof(struct menf21bmc_hwmon), + GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + drv_data->i2c_client = i2c_client; + + ret = menf21bmc_hwmon_get_volt_limits(drv_data); + if (ret) { + dev_err(&pdev->dev, "failed to read sensor limits"); + return ret; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, + "menf21bmc", drv_data, + menf21bmc_hwmon_groups); + if (IS_ERR(hwmon_dev)) + return PTR_ERR(hwmon_dev); + + dev_info(&pdev->dev, "MEN 14F021P00 BMC hwmon device enabled"); + + return 0; +} + +static struct platform_driver menf21bmc_hwmon = { + .probe = menf21bmc_hwmon_probe, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(menf21bmc_hwmon); + +MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); +MODULE_DESCRIPTION("MEN 14F021P00 BMC hwmon"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_hwmon"); diff --git a/drivers/hwmon/ntc_thermistor.c b/drivers/hwmon/ntc_thermistor.c index bd410722cd4b..4ff89b2482e4 100644 --- a/drivers/hwmon/ntc_thermistor.c +++ b/drivers/hwmon/ntc_thermistor.c @@ -38,6 +38,7 @@ #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> +#include <linux/thermal.h> struct ntc_compensation { int temp_c; @@ -182,6 +183,7 @@ struct ntc_data { struct device *dev; int n_comp; char name[PLATFORM_NAME_SIZE]; + struct thermal_zone_device *tz; }; #if defined(CONFIG_OF) && IS_ENABLED(CONFIG_IIO) @@ -428,6 +430,20 @@ static int ntc_thermistor_get_ohm(struct ntc_data *data) return -EINVAL; } +static int ntc_read_temp(void *dev, long *temp) +{ + struct ntc_data *data = dev_get_drvdata(dev); + int ohm; + + ohm = ntc_thermistor_get_ohm(data); + if (ohm < 0) + return ohm; + + *temp = get_temp_mc(data, ohm); + + return 0; +} + static ssize_t ntc_show_name(struct device *dev, struct device_attribute *attr, char *buf) { @@ -562,6 +578,13 @@ static int ntc_thermistor_probe(struct platform_device *pdev) dev_info(&pdev->dev, "Thermistor type: %s successfully probed.\n", pdev_id->name); + data->tz = thermal_zone_of_sensor_register(data->dev, 0, data->dev, + ntc_read_temp, NULL); + if (IS_ERR(data->tz)) { + dev_dbg(&pdev->dev, "Failed to register to thermal fw.\n"); + data->tz = NULL; + } + return 0; err_after_sysfs: sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); @@ -578,6 +601,8 @@ static int ntc_thermistor_remove(struct platform_device *pdev) sysfs_remove_group(&data->dev->kobj, &ntc_attr_group); ntc_iio_channel_release(pdata); + thermal_zone_of_sensor_unregister(data->dev, data->tz); + return 0; } diff --git a/drivers/hwmon/smsc47b397.c b/drivers/hwmon/smsc47b397.c index bd89e87bd6ae..221f0931bf1c 100644 --- a/drivers/hwmon/smsc47b397.c +++ b/drivers/hwmon/smsc47b397.c @@ -100,8 +100,6 @@ static u8 smsc47b397_reg_temp[] = {0x25, 0x26, 0x27, 0x80}; struct smsc47b397_data { unsigned short addr; - const char *name; - struct device *hwmon_dev; struct mutex lock; struct mutex update_lock; @@ -202,15 +200,7 @@ static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1); static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2); static SENSOR_DEVICE_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3); -static ssize_t show_name(struct device *dev, struct device_attribute - *devattr, char *buf) -{ - struct smsc47b397_data *data = dev_get_drvdata(dev); - return sprintf(buf, "%s\n", data->name); -} -static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); - -static struct attribute *smsc47b397_attributes[] = { +static struct attribute *smsc47b397_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, &sensor_dev_attr_temp2_input.dev_attr.attr, &sensor_dev_attr_temp3_input.dev_attr.attr, @@ -220,23 +210,10 @@ static struct attribute *smsc47b397_attributes[] = { &sensor_dev_attr_fan3_input.dev_attr.attr, &sensor_dev_attr_fan4_input.dev_attr.attr, - &dev_attr_name.attr, NULL }; -static const struct attribute_group smsc47b397_group = { - .attrs = smsc47b397_attributes, -}; - -static int smsc47b397_remove(struct platform_device *pdev) -{ - struct smsc47b397_data *data = platform_get_drvdata(pdev); - - hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&pdev->dev.kobj, &smsc47b397_group); - - return 0; -} +ATTRIBUTE_GROUPS(smsc47b397); static int smsc47b397_probe(struct platform_device *pdev); @@ -246,15 +223,14 @@ static struct platform_driver smsc47b397_driver = { .name = DRVNAME, }, .probe = smsc47b397_probe, - .remove = smsc47b397_remove, }; static int smsc47b397_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct smsc47b397_data *data; + struct device *hwmon_dev; struct resource *res; - int err = 0; res = platform_get_resource(pdev, IORESOURCE_IO, 0); if (!devm_request_region(dev, res->start, SMSC_EXTENT, @@ -270,26 +246,13 @@ static int smsc47b397_probe(struct platform_device *pdev) return -ENOMEM; data->addr = res->start; - data->name = "smsc47b397"; mutex_init(&data->lock); mutex_init(&data->update_lock); - platform_set_drvdata(pdev, data); - - err = sysfs_create_group(&dev->kobj, &smsc47b397_group); - if (err) - return err; - data->hwmon_dev = hwmon_device_register(dev); - if (IS_ERR(data->hwmon_dev)) { - err = PTR_ERR(data->hwmon_dev); - goto error_remove; - } - - return 0; - -error_remove: - sysfs_remove_group(&dev->kobj, &smsc47b397_group); - return err; + hwmon_dev = devm_hwmon_device_register_with_groups(dev, "smsc47b397", + data, + smsc47b397_groups); + return PTR_ERR_OR_ZERO(hwmon_dev); } static int __init smsc47b397_device_add(unsigned short address) diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 90e108f9e22e..a210338cfeb1 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -468,6 +468,15 @@ config LEDS_OT200 This option enables support for the LEDs on the Bachmann OT200. Say Y to enable LEDs on the Bachmann OT200. +config LEDS_MENF21BMC + tristate "LED support for the MEN 14F021P00 BMC" + depends on LEDS_CLASS && MFD_MENF21BMC + help + Say Y here to include support for the MEN 14F021P00 BMC LEDs. + + This driver can also be built as a module. If so the module + will be called leds-menf21bmc. + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" config LEDS_BLINKM diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 822dd83ef97a..a2b164741465 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o +obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/leds-menf21bmc.c b/drivers/leds/leds-menf21bmc.c new file mode 100644 index 000000000000..89dd57769e3b --- /dev/null +++ b/drivers/leds/leds-menf21bmc.c @@ -0,0 +1,131 @@ +/* + * MEN 14F021P00 Board Management Controller (BMC) LEDs Driver. + * + * This is the core LED driver of the MEN 14F021P00 BMC. + * There are four LEDs available which can be switched on and off. + * STATUS LED, HOT SWAP LED, USER LED 1, USER LED 2 + * + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/i2c.h> + +#define BMC_CMD_LED_GET_SET 0xA0 +#define BMC_BIT_LED_STATUS BIT(0) +#define BMC_BIT_LED_HOTSWAP BIT(1) +#define BMC_BIT_LED_USER1 BIT(2) +#define BMC_BIT_LED_USER2 BIT(3) + +struct menf21bmc_led { + struct led_classdev cdev; + u8 led_bit; + const char *name; + struct i2c_client *i2c_client; +}; + +static struct menf21bmc_led leds[] = { + { + .name = "menf21bmc:led_status", + .led_bit = BMC_BIT_LED_STATUS, + }, + { + .name = "menf21bmc:led_hotswap", + .led_bit = BMC_BIT_LED_HOTSWAP, + }, + { + .name = "menf21bmc:led_user1", + .led_bit = BMC_BIT_LED_USER1, + }, + { + .name = "menf21bmc:led_user2", + .led_bit = BMC_BIT_LED_USER2, + } +}; + +static DEFINE_MUTEX(led_lock); + +static void +menf21bmc_led_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + int led_val; + struct menf21bmc_led *led = container_of(led_cdev, + struct menf21bmc_led, cdev); + + mutex_lock(&led_lock); + led_val = i2c_smbus_read_byte_data(led->i2c_client, + BMC_CMD_LED_GET_SET); + if (led_val < 0) + goto err_out; + + if (value == LED_OFF) + led_val &= ~led->led_bit; + else + led_val |= led->led_bit; + + i2c_smbus_write_byte_data(led->i2c_client, + BMC_CMD_LED_GET_SET, led_val); +err_out: + mutex_unlock(&led_lock); +} + +static int menf21bmc_led_probe(struct platform_device *pdev) +{ + int i; + int ret; + struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent); + + for (i = 0; i < ARRAY_SIZE(leds); i++) { + leds[i].cdev.name = leds[i].name; + leds[i].cdev.brightness_set = menf21bmc_led_set; + leds[i].i2c_client = i2c_client; + ret = led_classdev_register(&pdev->dev, &leds[i].cdev); + if (ret < 0) + goto err_free_leds; + } + dev_info(&pdev->dev, "MEN 140F21P00 BMC LED device enabled\n"); + + return 0; + +err_free_leds: + dev_err(&pdev->dev, "failed to register LED device\n"); + + for (i = i - 1; i >= 0; i--) + led_classdev_unregister(&leds[i].cdev); + + return ret; +} + +static int menf21bmc_led_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(leds); i++) + led_classdev_unregister(&leds[i].cdev); + + return 0; +} + +static struct platform_driver menf21bmc_led = { + .probe = menf21bmc_led_probe, + .remove = menf21bmc_led_remove, + .driver = { + .name = "menf21bmc_led", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(menf21bmc_led); + +MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); +MODULE_DESCRIPTION("MEN 14F021P00 BMC led driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_led"); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index de5abf244746..cf66ef1ffaf3 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -454,6 +454,21 @@ config MFD_MAX8998 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MENF21BMC + tristate "MEN 14F021P00 Board Management Controller Support" + depends on I2C + select MFD_CORE + help + Say yes here to add support for the MEN 14F021P00 BMC + which is a Board Management Controller connected to the I2C bus. + The device supports multiple sub-devices like LED, HWMON and WDT. + This driver provides common support for accessing the devices; + additional drivers must be enabled in order to use the + functionality of the BMC device. + + This driver can also be built as a module. If so the module + will be called menf21bmc. + config EZX_PCAP bool "Motorola EZXPCAP Support" depends on SPI_MASTER diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f00148782d9b..d58068aa8aa9 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -169,6 +169,7 @@ obj-$(CONFIG_MFD_AS3711) += as3711.o obj-$(CONFIG_MFD_AS3722) += as3722.o obj-$(CONFIG_MFD_STW481X) += stw481x.o obj-$(CONFIG_MFD_IPAQ_MICRO) += ipaq-micro.o +obj-$(CONFIG_MFD_MENF21BMC) += menf21bmc.o intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o diff --git a/drivers/mfd/menf21bmc.c b/drivers/mfd/menf21bmc.c new file mode 100644 index 000000000000..1c274345820c --- /dev/null +++ b/drivers/mfd/menf21bmc.c @@ -0,0 +1,132 @@ +/* + * MEN 14F021P00 Board Management Controller (BMC) MFD Core Driver. + * + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/mfd/core.h> + +#define BMC_CMD_WDT_EXIT_PROD 0x18 +#define BMC_CMD_WDT_PROD_STAT 0x19 +#define BMC_CMD_REV_MAJOR 0x80 +#define BMC_CMD_REV_MINOR 0x81 +#define BMC_CMD_REV_MAIN 0x82 + +static struct mfd_cell menf21bmc_cell[] = { + { .name = "menf21bmc_wdt", }, + { .name = "menf21bmc_led", }, + { .name = "menf21bmc_hwmon", } +}; + +static int menf21bmc_wdt_exit_prod_mode(struct i2c_client *client) +{ + int val, ret; + + val = i2c_smbus_read_byte_data(client, BMC_CMD_WDT_PROD_STAT); + if (val < 0) + return val; + + /* + * Production mode should be not active after delivery of the Board. + * To be sure we check it, inform the user and exit the mode + * if active. + */ + if (val == 0x00) { + dev_info(&client->dev, + "BMC in production mode. Exit production mode\n"); + + ret = i2c_smbus_write_byte(client, BMC_CMD_WDT_EXIT_PROD); + if (ret < 0) + return ret; + } + + return 0; +} + +static int +menf21bmc_probe(struct i2c_client *client, const struct i2c_device_id *ids) +{ + int rev_major, rev_minor, rev_main; + int ret; + + ret = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BYTE); + if (!ret) + return -ENODEV; + + rev_major = i2c_smbus_read_word_data(client, BMC_CMD_REV_MAJOR); + if (rev_major < 0) { + dev_err(&client->dev, "failed to get BMC major revision\n"); + return rev_major; + } + + rev_minor = i2c_smbus_read_word_data(client, BMC_CMD_REV_MINOR); + if (rev_minor < 0) { + dev_err(&client->dev, "failed to get BMC minor revision\n"); + return rev_minor; + } + + rev_main = i2c_smbus_read_word_data(client, BMC_CMD_REV_MAIN); + if (rev_main < 0) { + dev_err(&client->dev, "failed to get BMC main revision\n"); + return rev_main; + } + + dev_info(&client->dev, "FW Revision: %02d.%02d.%02d\n", + rev_major, rev_minor, rev_main); + + /* + * We have to exit the Production Mode of the BMC to activate the + * Watchdog functionality and the BIOS life sign monitoring. + */ + ret = menf21bmc_wdt_exit_prod_mode(client); + if (ret < 0) { + dev_err(&client->dev, "failed to leave production mode\n"); + return ret; + } + + ret = mfd_add_devices(&client->dev, 0, menf21bmc_cell, + ARRAY_SIZE(menf21bmc_cell), NULL, 0, NULL); + if (ret < 0) { + dev_err(&client->dev, "failed to add BMC sub-devices\n"); + return ret; + } + + return 0; +} + +static int menf21bmc_remove(struct i2c_client *client) +{ + mfd_remove_devices(&client->dev); + return 0; +} + +static const struct i2c_device_id menf21bmc_id_table[] = { + { "menf21bmc" }, + { } +}; +MODULE_DEVICE_TABLE(i2c, menf21bmc_id_table); + +static struct i2c_driver menf21bmc_driver = { + .driver.name = "menf21bmc", + .id_table = menf21bmc_id_table, + .probe = menf21bmc_probe, + .remove = menf21bmc_remove, +}; + +module_i2c_driver(menf21bmc_driver); + +MODULE_DESCRIPTION("MEN 14F021P00 BMC mfd core driver"); +MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1d1330a78af3..e3d5bf0a5021 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -95,6 +95,16 @@ config GPIO_WATCHDOG If you say yes here you get support for watchdog device controlled through GPIO-line. +config MENF21BMC_WATCHDOG + tristate "MEN 14F021P00 BMC Watchdog" + depends on MFD_MENF21BMC + select WATCHDOG_CORE + help + Say Y here to include support for the MEN 14F021P00 BMC Watchdog. + + This driver can also be built as a module. If so the module + will be called menf21bmc_wdt. + config WM831X_WATCHDOG tristate "WM831x watchdog" depends on MFD_WM831X diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 468c3204c3b1..de1701470c14 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -178,3 +178,4 @@ obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o +obj-$(CONFIG_MENF21BMC_WATCHDOG) += menf21bmc_wdt.o diff --git a/drivers/watchdog/menf21bmc_wdt.c b/drivers/watchdog/menf21bmc_wdt.c new file mode 100644 index 000000000000..2042874d5ce3 --- /dev/null +++ b/drivers/watchdog/menf21bmc_wdt.c @@ -0,0 +1,203 @@ +/* + * MEN 14F021P00 Board Management Controller (BMC) Watchdog Driver. + * + * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/watchdog.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> + +#define DEVNAME "menf21bmc_wdt" + +#define BMC_CMD_WD_ON 0x11 +#define BMC_CMD_WD_OFF 0x12 +#define BMC_CMD_WD_TRIG 0x13 +#define BMC_CMD_WD_TIME 0x14 +#define BMC_CMD_WD_STATE 0x17 +#define BMC_WD_OFF_VAL 0x69 +#define BMC_CMD_RST_RSN 0x92 + +#define BMC_WD_TIMEOUT_MIN 1 /* in sec */ +#define BMC_WD_TIMEOUT_MAX 6553 /* in sec */ + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +struct menf21bmc_wdt { + struct watchdog_device wdt; + struct i2c_client *i2c_client; +}; + +static int menf21bmc_wdt_set_bootstatus(struct menf21bmc_wdt *data) +{ + int rst_rsn; + + rst_rsn = i2c_smbus_read_byte_data(data->i2c_client, BMC_CMD_RST_RSN); + if (rst_rsn < 0) + return rst_rsn; + + if (rst_rsn == 0x02) + data->wdt.bootstatus |= WDIOF_CARDRESET; + else if (rst_rsn == 0x05) + data->wdt.bootstatus |= WDIOF_EXTERN1; + else if (rst_rsn == 0x06) + data->wdt.bootstatus |= WDIOF_EXTERN2; + else if (rst_rsn == 0x0A) + data->wdt.bootstatus |= WDIOF_POWERUNDER; + + return 0; +} + +static int menf21bmc_wdt_start(struct watchdog_device *wdt) +{ + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); + + return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_ON); +} + +static int menf21bmc_wdt_stop(struct watchdog_device *wdt) +{ + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); + + return i2c_smbus_write_byte_data(drv_data->i2c_client, + BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); +} + +static int +menf21bmc_wdt_settimeout(struct watchdog_device *wdt, unsigned int timeout) +{ + int ret; + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); + + /* + * BMC Watchdog does have a resolution of 100ms. + * Watchdog API defines the timeout in seconds, so we have to + * multiply the value. + */ + ret = i2c_smbus_write_word_data(drv_data->i2c_client, + BMC_CMD_WD_TIME, timeout * 10); + if (ret < 0) + return ret; + + wdt->timeout = timeout; + + return 0; +} + +static int menf21bmc_wdt_ping(struct watchdog_device *wdt) +{ + struct menf21bmc_wdt *drv_data = watchdog_get_drvdata(wdt); + + return i2c_smbus_write_byte(drv_data->i2c_client, BMC_CMD_WD_TRIG); +} + +static const struct watchdog_info menf21bmc_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = DEVNAME, +}; + +static const struct watchdog_ops menf21bmc_wdt_ops = { + .owner = THIS_MODULE, + .start = menf21bmc_wdt_start, + .stop = menf21bmc_wdt_stop, + .ping = menf21bmc_wdt_ping, + .set_timeout = menf21bmc_wdt_settimeout, +}; + +static int menf21bmc_wdt_probe(struct platform_device *pdev) +{ + int ret, bmc_timeout; + struct menf21bmc_wdt *drv_data; + struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent); + + drv_data = devm_kzalloc(&pdev->dev, + sizeof(struct menf21bmc_wdt), GFP_KERNEL); + if (!drv_data) + return -ENOMEM; + + drv_data->wdt.ops = &menf21bmc_wdt_ops; + drv_data->wdt.info = &menf21bmc_wdt_info; + drv_data->wdt.min_timeout = BMC_WD_TIMEOUT_MIN; + drv_data->wdt.max_timeout = BMC_WD_TIMEOUT_MAX; + drv_data->i2c_client = i2c_client; + + /* + * Get the current wdt timeout value from the BMC because + * the BMC will save the value set before if the system restarts. + */ + bmc_timeout = i2c_smbus_read_word_data(drv_data->i2c_client, + BMC_CMD_WD_TIME); + if (bmc_timeout < 0) { + dev_err(&pdev->dev, "failed to get current WDT timeout\n"); + return bmc_timeout; + } + + watchdog_init_timeout(&drv_data->wdt, bmc_timeout / 10, &pdev->dev); + watchdog_set_nowayout(&drv_data->wdt, nowayout); + watchdog_set_drvdata(&drv_data->wdt, drv_data); + platform_set_drvdata(pdev, drv_data); + + ret = menf21bmc_wdt_set_bootstatus(drv_data); + if (ret < 0) { + dev_err(&pdev->dev, "failed to set Watchdog bootstatus\n"); + return ret; + } + + ret = watchdog_register_device(&drv_data->wdt); + if (ret) { + dev_err(&pdev->dev, "failed to register Watchdog device\n"); + return ret; + } + + dev_info(&pdev->dev, "MEN 14F021P00 BMC Watchdog device enabled\n"); + + return 0; +} + +static int menf21bmc_wdt_remove(struct platform_device *pdev) +{ + struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev); + + dev_warn(&pdev->dev, + "Unregister MEN 14F021P00 BMC Watchdog device, board may reset\n"); + + watchdog_unregister_device(&drv_data->wdt); + + return 0; +} + +static void menf21bmc_wdt_shutdown(struct platform_device *pdev) +{ + struct menf21bmc_wdt *drv_data = platform_get_drvdata(pdev); + + i2c_smbus_write_word_data(drv_data->i2c_client, + BMC_CMD_WD_OFF, BMC_WD_OFF_VAL); +} + +static struct platform_driver menf21bmc_wdt = { + .driver = { + .owner = THIS_MODULE, + .name = DEVNAME, + }, + .probe = menf21bmc_wdt_probe, + .remove = menf21bmc_wdt_remove, + .shutdown = menf21bmc_wdt_shutdown, +}; + +module_platform_driver(menf21bmc_wdt); + +MODULE_DESCRIPTION("MEN 14F021P00 BMC Watchdog driver"); +MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_wdt"); |