diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/menf21bmc_hwmon.c | 230 | ||||
-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/max14577.c | 100 | ||||
-rw-r--r-- | drivers/mfd/menf21bmc.c | 132 | ||||
-rw-r--r-- | drivers/power/Kconfig | 5 | ||||
-rw-r--r-- | drivers/power/max14577_charger.c | 370 | ||||
-rw-r--r-- | drivers/power/max17040_battery.c | 3 | ||||
-rw-r--r-- | drivers/regulator/max14577.c | 80 | ||||
-rw-r--r-- | drivers/watchdog/Kconfig | 10 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 1 | ||||
-rw-r--r-- | drivers/watchdog/menf21bmc_wdt.c | 203 |
17 files changed, 1188 insertions, 114 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f00d048..c08d172 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -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 diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index be28152..c90a761 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/menf21bmc_hwmon.c b/drivers/hwmon/menf21bmc_hwmon.c new file mode 100644 index 0000000..c92229d --- /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/leds/Kconfig b/drivers/leds/Kconfig index 8c96e2d..eadd56c 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 d8cc5f2..9a72fdd 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.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 0000000..89dd577 --- /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 de5abf2..cf66ef1 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 f001487..d58068a 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/max14577.c b/drivers/mfd/max14577.c index 4a5e885..b8af263 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -26,6 +26,87 @@ #include <linux/mfd/max14577.h> #include <linux/mfd/max14577-private.h> +/* + * Table of valid charger currents for different Maxim chipsets. + * It is placed here because it is used by both charger and regulator driver. + */ +const struct maxim_charger_current maxim_charger_currents[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, + [MAXIM_DEVICE_TYPE_MAX14577] = { + .min = MAX14577_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX14577_CHARGER_CURRENT_LIMIT_MAX, + }, + [MAXIM_DEVICE_TYPE_MAX77836] = { + .min = MAX77836_CHARGER_CURRENT_LIMIT_MIN, + .high_start = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START, + .high_step = MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP, + .max = MAX77836_CHARGER_CURRENT_LIMIT_MAX, + }, +}; +EXPORT_SYMBOL_GPL(maxim_charger_currents); + +/* + * maxim_charger_calc_reg_current - Calculate register value for current + * @limits: constraints for charger, matching the MBCICHWRC register + * @min_ua: minimal requested current, micro Amps + * @max_ua: maximum requested current, micro Amps + * @dst: destination to store calculated register value + * + * Calculates the value of MBCICHWRC (Fast Battery Charge Current) register + * for given current and stores it under pointed 'dst'. The stored value + * combines low bit (MBCICHWRCL) and high bits (MBCICHWRCH). It is also + * properly shifted. + * + * The calculated register value matches the current which: + * - is always between <limits.min, limits.max>; + * - is always less or equal to max_ua; + * - is the highest possible value; + * - may be lower than min_ua. + * + * On success returns 0. On error returns -EINVAL (requested min/max current + * is outside of given charger limits) and 'dst' is not set. + */ +int maxim_charger_calc_reg_current(const struct maxim_charger_current *limits, + unsigned int min_ua, unsigned int max_ua, u8 *dst) +{ + unsigned int current_bits = 0xf; + + if (min_ua > max_ua) + return -EINVAL; + + if (min_ua > limits->max || max_ua < limits->min) + return -EINVAL; + + if (max_ua < limits->high_start) { + /* + * Less than high_start, so set the minimal current + * (turn Low Bit off, 0 as high bits). + */ + *dst = 0x0; + return 0; + } + + /* max_ua is in range: <high_start, infinite>, cut it to limits.max */ + max_ua = min(limits->max, max_ua); + max_ua -= limits->high_start; + /* + * There is no risk of overflow 'max_ua' here because: + * - max_ua >= limits.high_start + * - BUILD_BUG checks that 'limits' are: max >= high_start + high_step + */ + current_bits = max_ua / limits->high_step; + + /* Turn Low Bit on (use range <limits.high_start, limits.max>) ... */ + *dst = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + /* and set proper High Bits */ + *dst |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + + return 0; +} +EXPORT_SYMBOL_GPL(maxim_charger_calc_reg_current); + static const struct mfd_cell max14577_devs[] = { { .name = "max14577-muic", @@ -35,7 +116,10 @@ static const struct mfd_cell max14577_devs[] = { .name = "max14577-regulator", .of_compatible = "maxim,max14577-regulator", }, - { .name = "max14577-charger", }, + { + .name = "max14577-charger", + .of_compatible = "maxim,max14577-charger", + }, }; static const struct mfd_cell max77836_devs[] = { @@ -463,6 +547,20 @@ static int __init max14577_i2c_init(void) BUILD_BUG_ON(ARRAY_SIZE(max14577_i2c_id) != MAXIM_DEVICE_TYPE_NUM); BUILD_BUG_ON(ARRAY_SIZE(max14577_dt_match) != MAXIM_DEVICE_TYPE_NUM); + /* Valid charger current values must be provided for each chipset */ + BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); + + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX14577_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START + + MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP * 0xf != + MAX77836_CHARGER_CURRENT_LIMIT_MAX); + BUILD_BUG_ON(MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP == 0); + return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); diff --git a/drivers/mfd/menf21bmc.c b/drivers/mfd/menf21bmc.c new file mode 100644 index 0000000..1c27434 --- /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/power/Kconfig b/drivers/power/Kconfig index 73cfcdf..04e1d2f 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -325,11 +325,12 @@ config CHARGER_MANAGER with help of suspend_again support. config CHARGER_MAX14577 - tristate "Maxim MAX14577 MUIC battery charger driver" + tristate "Maxim MAX14577/77836 battery charger driver" depends on MFD_MAX14577 + select SYSFS help Say Y to enable support for the battery charger control sysfs and - platform data of MAX14577 MUICs. + platform data of MAX14577/77836 MUICs. config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c index fad2a75..0a2bc72 100644 --- a/drivers/power/max14577_charger.c +++ b/drivers/power/max14577_charger.c @@ -1,7 +1,7 @@ /* - * Battery charger driver for the Maxim 14577 + * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electronics + * Copyright (C) 2013,2014 Samsung Electronics * Krzysztof Kozlowski <k.kozlowski@samsung.com> * * This program is free software; you can redistribute it and/or modify @@ -19,16 +19,44 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/mfd/max14577-private.h> +#include <linux/mfd/max14577.h> struct max14577_charger { struct device *dev; struct max14577 *max14577; struct power_supply charger; - unsigned int charging_state; - unsigned int battery_state; + unsigned int charging_state; + unsigned int battery_state; + + struct max14577_charger_platform_data *pdata; }; +/* + * Helper function for mapping values of STATUS2/CHGTYP register on max14577 + * and max77836 chipsets to enum maxim_muic_charger_type. + */ +static enum max14577_muic_charger_type maxim_get_charger_type( + enum maxim_device_type dev_type, u8 val) { + switch (val) { + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_USB: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_DEDICATED_CHG: + case MAX14577_CHARGER_TYPE_SPECIAL_500MA: + case MAX14577_CHARGER_TYPE_SPECIAL_1A: + return val; + case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX14577_CHARGER_TYPE_RESERVED: + if (dev_type == MAXIM_DEVICE_TYPE_MAX77836) + val |= 0x8; + return val; + default: + WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val); + return val; + } +} + static int max14577_get_charger_state(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; @@ -89,19 +117,23 @@ static int max14577_get_online(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; u8 reg_data; + enum max14577_muic_charger_type chg_type; max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - switch (reg_data) { + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + switch (chg_type) { case MAX14577_CHARGER_TYPE_USB: case MAX14577_CHARGER_TYPE_DEDICATED_CHG: case MAX14577_CHARGER_TYPE_SPECIAL_500MA: case MAX14577_CHARGER_TYPE_SPECIAL_1A: case MAX14577_CHARGER_TYPE_DEAD_BATTERY: + case MAX77836_CHARGER_TYPE_SPECIAL_BIAS: return 1; case MAX14577_CHARGER_TYPE_NONE: case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: case MAX14577_CHARGER_TYPE_RESERVED: + case MAX77836_CHARGER_TYPE_RESERVED: default: return 0; } @@ -118,10 +150,12 @@ static int max14577_get_battery_health(struct max14577_charger *chg) struct regmap *rmap = chg->max14577->regmap; int state = POWER_SUPPLY_HEALTH_GOOD; u8 reg_data; + enum max14577_muic_charger_type chg_type; max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); - if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data); + if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { state = POWER_SUPPLY_HEALTH_DEAD; goto state_set; } @@ -147,15 +181,131 @@ static int max14577_get_present(struct max14577_charger *chg) return 1; } +static int max14577_set_fast_charge_timer(struct max14577_charger *chg, + unsigned long hours) +{ + u8 reg_data; + + switch (hours) { + case 5 ... 7: + reg_data = hours - 3; + break; + case 0: + /* Disable */ + reg_data = 0x7; + break; + default: + dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n", + hours); + return -EINVAL; + } + reg_data <<= CHGCTRL1_TCHW_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data); +} + +static int max14577_init_constant_voltage(struct max14577_charger *chg, + unsigned int uvolt) +{ + u8 reg_data; + + if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN || + uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + return -EINVAL; + + if (uvolt == 4200000) + reg_data = 0x0; + else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX) + reg_data = 0x1f; + else if (uvolt <= 4280000) { + unsigned int val = uvolt; + + val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN; + val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP; + if (uvolt <= 4180000) + reg_data = 0x1 + val; + else + reg_data = val; /* Fix for gap between 4.18V and 4.22V */ + } else + return -EINVAL; + + reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT; + + return max14577_write_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL3, reg_data); +} + +static int max14577_init_eoc(struct max14577_charger *chg, + unsigned int uamp) +{ + unsigned int current_bits = 0xf; + u8 reg_data; + + switch (chg->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + if (uamp < 5000) + return -EINVAL; /* Requested current is too low */ + + if (uamp >= 7500 && uamp < 10000) + current_bits = 0x0; + else if (uamp <= 50000) { + /* <5000, 7499> and <10000, 50000> */ + current_bits = uamp / 5000; + } else { + uamp = min(uamp, 100000U) - 50000U; + current_bits = 0xa + uamp / 10000; + } + break; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN) + return -EINVAL; /* Requested current is too low */ + + uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); + uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN; + current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP; + break; + } + + reg_data = current_bits << CHGCTRL5_EOCS_SHIFT; + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK, + reg_data); +} + +static int max14577_init_fast_charge(struct max14577_charger *chg, + unsigned int uamp) +{ + u8 reg_data; + int ret; + const struct maxim_charger_current *limits = + &maxim_charger_currents[chg->max14577->dev_type]; + + ret = maxim_charger_calc_reg_current(limits, uamp, uamp, ®_data); + if (ret) { + dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp); + return ret; + } + + return max14577_update_reg(chg->max14577->regmap, + MAX14577_CHG_REG_CHG_CTRL4, + CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, + reg_data); +} + /* * Sets charger registers to proper and safe default values. * Some of these values are equal to defaults in MAX14577E * data sheet but there are minor differences. */ -static void max14577_charger_reg_init(struct max14577_charger *chg) +static int max14577_charger_reg_init(struct max14577_charger *chg) { struct regmap *rmap = chg->max14577->regmap; u8 reg_data; + int ret; /* * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0) @@ -167,10 +317,6 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK, reg_data); - /* Battery Fast-Charge Timer, from SM-V700: 6hrs */ - reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data); - /* * Wall-Adapter Rapid Charge, default on * Battery-Charger, default on @@ -179,29 +325,46 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data); - /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */ - reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data); - - /* - * Fast Battery-Charge Current Low, default 200-950mA - * Fast Battery-Charge Current High, from SM-V700: 450mA - */ - reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; - reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data); - - /* End-of-Charge Current, from SM-V700: 50mA */ - reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; - max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); - /* Auto Charging Stop, default off */ reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); - /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ - reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; + ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt); + if (ret) + return ret; + + ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp); + if (ret) + return ret; + + ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, + MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT); + if (ret) + return ret; + + /* Initialize Overvoltage-Protection Threshold */ + switch (chg->pdata->ovp_uvolt) { + case 7500000: + reg_data = 0x0; + break; + case 6000000: + case 6500000: + case 7000000: + reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000; + break; + default: + dev_err(chg->dev, "Wrong value for OVP: %u\n", + chg->pdata->ovp_uvolt); + return -EINVAL; + } + reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); + + return 0; } /* Support property from charger */ @@ -215,7 +378,11 @@ static enum power_supply_property max14577_charger_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, }; -static const char *model_name = "MAX14577"; +static const char * const model_names[] = { + [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like", + [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577", + [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836", +}; static const char *manufacturer = "Maxim Integrated"; static int max14577_charger_get_property(struct power_supply *psy, @@ -244,7 +411,8 @@ static int max14577_charger_get_property(struct power_supply *psy, val->intval = max14577_get_online(chg); break; case POWER_SUPPLY_PROP_MODEL_NAME: - val->strval = model_name; + BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM); + val->strval = model_names[chg->max14577->dev_type]; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = manufacturer; @@ -256,6 +424,110 @@ static int max14577_charger_get_property(struct power_supply *psy, return ret; } +#ifdef CONFIG_OF +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + struct max14577_charger_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + int ret; + + if (!np) { + dev_err(&pdev->dev, "No charger OF node\n"); + return ERR_PTR(-EINVAL); + } + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + ret = of_property_read_u32(np, "maxim,constant-uvolt", + &pdata->constant_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,fast-charge-uamp", + &pdata->fast_charge_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n"); + return ERR_PTR(ret); + } + + ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt); + if (ret) { + dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n"); + return ERR_PTR(ret); + } + + return pdata; +} +#else /* CONFIG_OF */ +static struct max14577_charger_platform_data *max14577_charger_dt_init( + struct platform_device *pdev) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +static ssize_t show_fast_charge_timer(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + u8 reg_data; + int ret; + unsigned int val; + + ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1, + ®_data); + if (ret) + return ret; + + reg_data &= CHGCTRL1_TCHW_MASK; + reg_data >>= CHGCTRL1_TCHW_SHIFT; + switch (reg_data) { + case 0x2 ... 0x4: + val = reg_data + 3; + break; + case 0x7: + val = 0; + break; + default: + val = 5; + break; + } + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_fast_charge_timer(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct max14577_charger *chg = dev_get_drvdata(dev); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret) + return ret; + + ret = max14577_set_fast_charge_timer(chg, val); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR, + show_fast_charge_timer, store_fast_charge_timer); + static int max14577_charger_probe(struct platform_device *pdev) { struct max14577_charger *chg; @@ -270,7 +542,13 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->dev = &pdev->dev; chg->max14577 = max14577; - max14577_charger_reg_init(chg); + chg->pdata = max14577_charger_dt_init(pdev); + if (IS_ERR_OR_NULL(chg->pdata)) + return PTR_ERR(chg->pdata); + + ret = max14577_charger_reg_init(chg); + if (ret) + return ret; chg->charger.name = "max14577-charger", chg->charger.type = POWER_SUPPLY_TYPE_BATTERY, @@ -278,24 +556,47 @@ static int max14577_charger_probe(struct platform_device *pdev) chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props), chg->charger.get_property = max14577_charger_get_property, + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); + if (ret) { + dev_err(&pdev->dev, "failed: create sysfs entry\n"); + return ret; + } + ret = power_supply_register(&pdev->dev, &chg->charger); if (ret) { dev_err(&pdev->dev, "failed: power supply register\n"); - return ret; + goto err; } + /* Check for valid values for charger */ + BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN + + MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf != + MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX); return 0; + +err: + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); + + return ret; } static int max14577_charger_remove(struct platform_device *pdev) { struct max14577_charger *chg = platform_get_drvdata(pdev); + device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer); power_supply_unregister(&chg->charger); return 0; } +static const struct platform_device_id max14577_charger_id[] = { + { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_charger_id); + static struct platform_driver max14577_charger_driver = { .driver = { .owner = THIS_MODULE, @@ -303,9 +604,10 @@ static struct platform_driver max14577_charger_driver = { }, .probe = max14577_charger_probe, .remove = max14577_charger_remove, + .id_table = max14577_charger_id, }; module_platform_driver(max14577_charger_driver); MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); -MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 0fbac86..14d4470 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -277,7 +277,8 @@ static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); #endif /* CONFIG_PM_SLEEP */ static const struct i2c_device_id max17040_id[] = { - { "max17040", 0 }, + { "max17040" }, + { "max77836-battery" }, { } }; MODULE_DEVICE_TABLE(i2c, max17040_id); diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c index 5d9c605..0ff5a20 100644 --- a/drivers/regulator/max14577.c +++ b/drivers/regulator/max14577.c @@ -22,42 +22,6 @@ #include <linux/mfd/max14577-private.h> #include <linux/regulator/of_regulator.h> -/* - * Valid limits of current for max14577 and max77836 chargers. - * They must correspond to MBCICHWRCL and MBCICHWRCH fields in CHGCTRL4 - * register for given chipset. - */ -struct maxim_charger_current { - /* Minimal current, set in CHGCTRL4/MBCICHWRCL, uA */ - unsigned int min; - /* - * Minimal current when high setting is active, - * set in CHGCTRL4/MBCICHWRCH, uA - */ - unsigned int high_start; - /* Value of one step in high setting, uA */ - unsigned int high_step; - /* Maximum current of high setting, uA */ - unsigned int max; -}; - -/* Table of valid charger currents for different Maxim chipsets */ -static const struct maxim_charger_current maxim_charger_currents[] = { - [MAXIM_DEVICE_TYPE_UNKNOWN] = { 0, 0, 0, 0 }, - [MAXIM_DEVICE_TYPE_MAX14577] = { - .min = MAX14577_REGULATOR_CURRENT_LIMIT_MIN, - .high_start = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START, - .high_step = MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP, - .max = MAX14577_REGULATOR_CURRENT_LIMIT_MAX, - }, - [MAXIM_DEVICE_TYPE_MAX77836] = { - .min = MAX77836_REGULATOR_CURRENT_LIMIT_MIN, - .high_start = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START, - .high_step = MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP, - .max = MAX77836_REGULATOR_CURRENT_LIMIT_MAX, - }, -}; - static int max14577_reg_is_enabled(struct regulator_dev *rdev) { int rid = rdev_get_id(rdev); @@ -103,8 +67,8 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) static int max14577_reg_set_current_limit(struct regulator_dev *rdev, int min_uA, int max_uA) { - int i, current_bits = 0xf; u8 reg_data; + int ret; struct max14577 *max14577 = rdev_get_drvdata(rdev); const struct maxim_charger_current *limits = &maxim_charger_currents[max14577->dev_type]; @@ -112,35 +76,9 @@ static int max14577_reg_set_current_limit(struct regulator_dev *rdev, if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; - if (min_uA > limits->max || max_uA < limits->min) - return -EINVAL; - - if (max_uA < limits->high_start) { - /* - * Less than high_start, - * so set the minimal current (turn only Low Bit off) - */ - u8 reg_data = 0x0 << CHGCTRL4_MBCICHWRCL_SHIFT; - return max14577_update_reg(rdev->regmap, - MAX14577_CHG_REG_CHG_CTRL4, - CHGCTRL4_MBCICHWRCL_MASK, reg_data); - } - - /* - * max_uA is in range: <high_start, inifinite>, so search for - * valid current starting from maximum current. - */ - for (i = limits->max; i >= limits->high_start; i -= limits->high_step) { - if (i <= max_uA) - break; - current_bits--; - } - BUG_ON(current_bits < 0); /* Cannot happen */ - - /* Turn Low Bit on (use range high_start-max)... */ - reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; - /* and set proper High Bits */ - reg_data |= current_bits << CHGCTRL4_MBCICHWRCH_SHIFT; + ret = maxim_charger_calc_reg_current(limits, min_uA, max_uA, ®_data); + if (ret) + return ret; return max14577_update_reg(rdev->regmap, MAX14577_CHG_REG_CHG_CTRL4, CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK, @@ -442,16 +380,6 @@ static struct platform_driver max14577_regulator_driver = { static int __init max14577_regulator_init(void) { - /* Check for valid values for charger */ - BUILD_BUG_ON(MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + - MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != - MAX14577_REGULATOR_CURRENT_LIMIT_MAX); - BUILD_BUG_ON(MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START + - MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP * 0xf != - MAX77836_REGULATOR_CURRENT_LIMIT_MAX); - /* Valid charger current values must be provided for each chipset */ - BUILD_BUG_ON(ARRAY_SIZE(maxim_charger_currents) != MAXIM_DEVICE_TYPE_NUM); - BUILD_BUG_ON(ARRAY_SIZE(max14577_supported_regulators) != MAX14577_REGULATOR_NUM); BUILD_BUG_ON(ARRAY_SIZE(max77836_supported_regulators) != MAX77836_REGULATOR_NUM); diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index f57312f..a50828e 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 468c320..de17014 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 0000000..2042874 --- /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"); |