From dfbdcd7cefcaba306fc1ad36aa8bc1352149e730 Mon Sep 17 00:00:00 2001 From: Andreas Werner Date: Wed, 27 Aug 2014 19:51:45 +0200 Subject: [PATCH 01/12] mfd: menf21bmc: Introduce MEN 14F021P00 BMC MFD Core driver The MEN 14F021P00 Board Management Controller provides an I2C interface to the host to access the feature implemented in the BMC. The BMC is a PIC Microntroller assembled on CPCI Card from MEN Mikroelektronik and on a few Box/Display Computer. Added MFD Core driver, supporting the I2C communication to the device. The MFD driver currently supports the following features: - Watchdog - LEDs - Hwmon (voltage monitoring) Signed-off-by: Andreas Werner Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 15 +++++ drivers/mfd/Makefile | 1 + drivers/mfd/menf21bmc.c | 132 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 drivers/mfd/menf21bmc.c 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 +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); From 25cc24c200dcba21bd1f1a59a01741185062dc0e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:53 +0200 Subject: [PATCH 02/12] mfd: max14577: Add defines for MAX77836 charger Prepare for adding support for MAX77836 charger to the max14577 charger driver by adding necessary new defines and prefixes to existing ones. The MAX77836 uses slightly different values for ChgTyp field of STATUS2 register. On the MAX14577 value of 0x6 is reserved and 0x7 dead battery. On the MAX77836 the opposite: - 0x6 means special charger, - 0x7 is reserved. Regardless of these differences use one common enum max14577_muic_charger_type. Signed-off-by: Krzysztof Kozlowski Cc: Kyungmin Park Acked-by: Lee Jones Acked-by: Andrew Morton --- include/linux/mfd/max14577-private.h | 54 ++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index 499253604026..d6f321699b89 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -72,15 +72,33 @@ enum max14577_muic_reg { MAX14577_MUIC_REG_END, }; +/* + * Combined charger types for max14577 and max77836. + * + * On max14577 three lower bits map to STATUS2/CHGTYP field. + * However the max77836 has different two last values of STATUS2/CHGTYP. + * To indicate the difference enum has two additional values for max77836. + * These values are just a register value bitwise OR with 0x8. + */ enum max14577_muic_charger_type { - MAX14577_CHARGER_TYPE_NONE = 0, - MAX14577_CHARGER_TYPE_USB, - MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT, - MAX14577_CHARGER_TYPE_DEDICATED_CHG, - MAX14577_CHARGER_TYPE_SPECIAL_500MA, - MAX14577_CHARGER_TYPE_SPECIAL_1A, - MAX14577_CHARGER_TYPE_RESERVED, - MAX14577_CHARGER_TYPE_DEAD_BATTERY = 7, + MAX14577_CHARGER_TYPE_NONE = 0x0, + MAX14577_CHARGER_TYPE_USB = 0x1, + MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT = 0x2, + MAX14577_CHARGER_TYPE_DEDICATED_CHG = 0x3, + MAX14577_CHARGER_TYPE_SPECIAL_500MA = 0x4, + /* Special 1A or 2A charger */ + MAX14577_CHARGER_TYPE_SPECIAL_1A = 0x5, + /* max14577: reserved, used on max77836 */ + MAX14577_CHARGER_TYPE_RESERVED = 0x6, + /* max14577: dead-battery charing with maximum current 100mA */ + MAX14577_CHARGER_TYPE_DEAD_BATTERY = 0x7, + /* + * max77836: special charger (bias on D+/D-), + * matches register value of 0x6 + */ + MAX77836_CHARGER_TYPE_SPECIAL_BIAS = 0xe, + /* max77836: reserved, register value 0x7 */ + MAX77836_CHARGER_TYPE_RESERVED = 0xf, }; /* MAX14577 interrupts */ @@ -121,13 +139,15 @@ enum max14577_muic_charger_type { #define STATUS2_CHGTYP_SHIFT 0 #define STATUS2_CHGDETRUN_SHIFT 3 #define STATUS2_DCDTMR_SHIFT 4 -#define STATUS2_DBCHG_SHIFT 5 +#define MAX14577_STATUS2_DBCHG_SHIFT 5 +#define MAX77836_STATUS2_DXOVP_SHIFT 5 #define STATUS2_VBVOLT_SHIFT 6 #define MAX77836_STATUS2_VIDRM_SHIFT 7 #define STATUS2_CHGTYP_MASK (0x7 << STATUS2_CHGTYP_SHIFT) #define STATUS2_CHGDETRUN_MASK BIT(STATUS2_CHGDETRUN_SHIFT) #define STATUS2_DCDTMR_MASK BIT(STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK BIT(STATUS2_DBCHG_SHIFT) +#define MAX14577_STATUS2_DBCHG_MASK BIT(MAX14577_STATUS2_DBCHG_SHIFT) +#define MAX77836_STATUS2_DXOVP_MASK BIT(MAX77836_STATUS2_DXOVP_SHIFT) #define STATUS2_VBVOLT_MASK BIT(STATUS2_VBVOLT_SHIFT) #define MAX77836_STATUS2_VIDRM_MASK BIT(MAX77836_STATUS2_VIDRM_SHIFT) @@ -177,9 +197,11 @@ enum max14577_muic_charger_type { #define CTRL3_JIGSET_SHIFT 0 #define CTRL3_BOOTSET_SHIFT 2 #define CTRL3_ADCDBSET_SHIFT 4 +#define CTRL3_WBTH_SHIFT 6 #define CTRL3_JIGSET_MASK (0x3 << CTRL3_JIGSET_SHIFT) #define CTRL3_BOOTSET_MASK (0x3 << CTRL3_BOOTSET_SHIFT) #define CTRL3_ADCDBSET_MASK (0x3 << CTRL3_ADCDBSET_SHIFT) +#define CTRL3_WBTH_MASK (0x3 << CTRL3_WBTH_SHIFT) /* Slave addr = 0x4A: Charger */ enum max14577_charger_reg { @@ -210,16 +232,20 @@ enum max14577_charger_reg { #define CDETCTRL1_CHGTYPMAN_SHIFT 1 #define CDETCTRL1_DCDEN_SHIFT 2 #define CDETCTRL1_DCD2SCT_SHIFT 3 -#define CDETCTRL1_DCHKTM_SHIFT 4 -#define CDETCTRL1_DBEXIT_SHIFT 5 +#define MAX14577_CDETCTRL1_DCHKTM_SHIFT 4 +#define MAX77836_CDETCTRL1_CDLY_SHIFT 4 +#define MAX14577_CDETCTRL1_DBEXIT_SHIFT 5 +#define MAX77836_CDETCTRL1_DCDCPL_SHIFT 5 #define CDETCTRL1_DBIDLE_SHIFT 6 #define CDETCTRL1_CDPDET_SHIFT 7 #define CDETCTRL1_CHGDETEN_MASK BIT(CDETCTRL1_CHGDETEN_SHIFT) #define CDETCTRL1_CHGTYPMAN_MASK BIT(CDETCTRL1_CHGTYPMAN_SHIFT) #define CDETCTRL1_DCDEN_MASK BIT(CDETCTRL1_DCDEN_SHIFT) #define CDETCTRL1_DCD2SCT_MASK BIT(CDETCTRL1_DCD2SCT_SHIFT) -#define CDETCTRL1_DCHKTM_MASK BIT(CDETCTRL1_DCHKTM_SHIFT) -#define CDETCTRL1_DBEXIT_MASK BIT(CDETCTRL1_DBEXIT_SHIFT) +#define MAX14577_CDETCTRL1_DCHKTM_MASK BIT(MAX14577_CDETCTRL1_DCHKTM_SHIFT) +#define MAX77836_CDETCTRL1_CDDLY_MASK BIT(MAX77836_CDETCTRL1_CDDLY_SHIFT) +#define MAX14577_CDETCTRL1_DBEXIT_MASK BIT(MAX14577_CDETCTRL1_DBEXIT_SHIFT) +#define MAX77836_CDETCTRL1_DCDCPL_MASK BIT(MAX77836_CDETCTRL1_DCDCPL_SHIFT) #define CDETCTRL1_DBIDLE_MASK BIT(CDETCTRL1_DBIDLE_SHIFT) #define CDETCTRL1_CDPDET_MASK BIT(CDETCTRL1_CDPDET_SHIFT) From 4476767cff44737f8965b34af0f375ac49f7fafa Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:54 +0200 Subject: [PATCH 03/12] mfd: max14577: Map charger device to its own of_node Add a "maxim,max14577-charger" of_compatible to the mfd_cell so the MFD child device (the charger) will have its own of_node set. This will be used by the max14577 charger driver in next patches to obtain battery configuration from DTS. Signed-off-by: Krzysztof Kozlowski Acked-by: Andrew Morton Signed-off-by: Lee Jones --- drivers/mfd/max14577.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 4a5e885383f8..6599407b5624 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -35,7 +35,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[] = { From 3682a8ee87f9107253e51733f42da10160ce41e3 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:55 +0200 Subject: [PATCH 04/12] charger: max14577: Add support for MAX77836 charger Add support for MAX77836 charger to the max14577 driver. The MAX77836 charger is almost the same as 14577 model except: - No dead-battery detection; - Support for special charger (like in MAX77693); - Support for DX over-voltage protection (like in MAX77693); - Lower values of charging current (two times lower current for slow/fast charge, much lower EOC current); - Slightly different values in ChgTyp field of STATUS2 register. On MAX14577 0x6 is reserved and 0x7 dead battery. On the MAX77836 the 0x6 means special charger and 0x7 is reserved. Regardless of these differences the driver maps them to one enum max14577_muic_charger_type. Signed-off-by: Krzysztof Kozlowski Acked-by: Andrew Morton Signed-off-by: Lee Jones --- drivers/power/Kconfig | 4 +- drivers/power/max14577_charger.c | 77 +++++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 17 deletions(-) diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 73cfcdf28a36..69fa8a9ef7a6 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -325,11 +325,11 @@ 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 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 fad2a75b3604..193530549b1f 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 * * This program is free software; you can redistribute it and/or modify @@ -25,10 +25,35 @@ struct max14577_charger { struct max14577 *max14577; struct power_supply charger; - unsigned int charging_state; - unsigned int battery_state; + unsigned int charging_state; + unsigned int battery_state; }; +/* + * 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 +114,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 +147,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; } @@ -167,7 +198,7 @@ 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 */ + /* Battery Fast-Charge Timer, set to: 6hrs */ reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, reg_data); @@ -179,19 +210,22 @@ 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 */ + /* Battery-Charger Constant Voltage (CV) Mode, set to: 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 + * Fast Battery-Charge Current Low, + * default 200-950mA (max14577) / 100-475mA (max77836) + * + * Fast Battery-Charge Current High, + * set to 450mA (max14577) / 225mA (max77836) */ 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 */ + /* End-of-Charge Current, set to 50mA (max14577) / 7.5mA (max77836) */ reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); @@ -199,7 +233,7 @@ static void max14577_charger_reg_init(struct max14577_charger *chg) reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); - /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ + /* Overvoltage-Protection Threshold, set to 6.5V */ reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); } @@ -215,7 +249,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 +282,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; @@ -296,6 +335,13 @@ static int max14577_charger_remove(struct platform_device *pdev) 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 +349,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 "); -MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); MODULE_LICENSE("GPL"); From b8f139f68f2099b7f8b4ef470a1e53210e3aa025 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:56 +0200 Subject: [PATCH 05/12] regulator/mfd: max14577: Export symbols for calculating charger current This patch prepares for changing the max14577 charger driver to allow configuring battery-dependent settings from DTS. The patch moves from regulator driver to MFD core driver and exports: - function for calculating register value for charger's current; - table of limits for chargers (MAX14577, MAX77836). Previously they were used only by the max14577 regulator driver. In next patch the charger driver will use them as well. Exporting them will reduce unnecessary code duplication. Signed-off-by: Krzysztof Kozlowski Acked-by: Mark Brown Acked-by: Andrew Morton Signed-off-by: Lee Jones --- drivers/mfd/max14577.c | 95 ++++++++++++++++++++++++++++ drivers/regulator/max14577.c | 80 ++--------------------- include/linux/mfd/max14577-private.h | 20 +++--- include/linux/mfd/max14577.h | 23 +++++++ 4 files changed, 132 insertions(+), 86 deletions(-) diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 6599407b5624..b8af263be594 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -26,6 +26,87 @@ #include #include +/* + * 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 ; + * - 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: , 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 ) ... */ + *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", @@ -466,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/regulator/max14577.c b/drivers/regulator/max14577.c index 5d9c605cf534..0ff5a20ac958 100644 --- a/drivers/regulator/max14577.c +++ b/drivers/regulator/max14577.c @@ -22,42 +22,6 @@ #include #include -/* - * 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: , 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/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index d6f321699b89..7d514839c764 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -281,17 +281,17 @@ enum max14577_charger_reg { #define CHGCTRL7_OTPCGHCVS_SHIFT 0 #define CHGCTRL7_OTPCGHCVS_MASK (0x3 << CHGCTRL7_OTPCGHCVS_SHIFT) -/* MAX14577 regulator current limits (as in CHGCTRL4 register), uA */ -#define MAX14577_REGULATOR_CURRENT_LIMIT_MIN 90000 -#define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START 200000 -#define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP 50000 -#define MAX14577_REGULATOR_CURRENT_LIMIT_MAX 950000 +/* MAX14577 charger current limits (as in CHGCTRL4 register), uA */ +#define MAX14577_CHARGER_CURRENT_LIMIT_MIN 90000U +#define MAX14577_CHARGER_CURRENT_LIMIT_HIGH_START 200000U +#define MAX14577_CHARGER_CURRENT_LIMIT_HIGH_STEP 50000U +#define MAX14577_CHARGER_CURRENT_LIMIT_MAX 950000U -/* MAX77836 regulator current limits (as in CHGCTRL4 register), uA */ -#define MAX77836_REGULATOR_CURRENT_LIMIT_MIN 45000 -#define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_START 100000 -#define MAX77836_REGULATOR_CURRENT_LIMIT_HIGH_STEP 25000 -#define MAX77836_REGULATOR_CURRENT_LIMIT_MAX 475000 +/* MAX77836 charger current limits (as in CHGCTRL4 register), uA */ +#define MAX77836_CHARGER_CURRENT_LIMIT_MIN 45000U +#define MAX77836_CHARGER_CURRENT_LIMIT_HIGH_START 100000U +#define MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP 25000U +#define MAX77836_CHARGER_CURRENT_LIMIT_MAX 475000U /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000 diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h index c83fbed1c7b6..3c098d57b1d1 100644 --- a/include/linux/mfd/max14577.h +++ b/include/linux/mfd/max14577.h @@ -74,4 +74,27 @@ struct max14577_platform_data { struct max14577_regulator_platform_data *regulators; }; +/* + * 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; +}; + +extern const struct maxim_charger_current maxim_charger_currents[]; +extern int maxim_charger_calc_reg_current(const struct maxim_charger_current *limits, + unsigned int min_ua, unsigned int max_ua, u8 *dst); + #endif /* __MAX14577_H__ */ From e30110e9c96f48aea01abc3e6dfadb369cbafec3 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:57 +0200 Subject: [PATCH 06/12] charger: max14577: Configure battery-dependent settings from DTS and sysfs Remove hard-coded values for: - Fast Charge current, - End Of Charge current, - Fast Charge timer, - Overvoltage Protection Threshold, - Battery Constant Voltage, and use DTS or sysfs to configure them. This allows using the max14577 charger driver with different batteries. Now the charger driver requires valid configuration data from DTS. In case of wrong configuration data it fails during probe. The fast charge timer is configured through sysfs entry. Signed-off-by: Krzysztof Kozlowski Acked-by: Andrew Morton Signed-off-by: Lee Jones --- drivers/power/Kconfig | 1 + drivers/power/max14577_charger.c | 313 ++++++++++++++++++++++++--- include/linux/mfd/max14577-private.h | 19 ++ include/linux/mfd/max14577.h | 7 + 4 files changed, 311 insertions(+), 29 deletions(-) diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 69fa8a9ef7a6..04e1d2fe2201 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -327,6 +327,7 @@ config CHARGER_MANAGER config CHARGER_MAX14577 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/77836 MUICs. diff --git a/drivers/power/max14577_charger.c b/drivers/power/max14577_charger.c index 193530549b1f..0a2bc7277026 100644 --- a/drivers/power/max14577_charger.c +++ b/drivers/power/max14577_charger.c @@ -19,6 +19,7 @@ #include #include #include +#include struct max14577_charger { struct device *dev; @@ -27,6 +28,8 @@ struct max14577_charger { unsigned int charging_state; unsigned int battery_state; + + struct max14577_charger_platform_data *pdata; }; /* @@ -178,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) @@ -198,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, set to: 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 @@ -210,32 +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, set to: 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 (max14577) / 100-475mA (max77836) - * - * Fast Battery-Charge Current High, - * set to 450mA (max14577) / 225mA (max77836) - */ - 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, set to 50mA (max14577) / 7.5mA (max77836) */ - 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, set to 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 */ @@ -295,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; @@ -309,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, @@ -317,19 +556,35 @@ 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 = power_supply_register(&pdev->dev, &chg->charger); + ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer); if (ret) { - dev_err(&pdev->dev, "failed: power supply register\n"); + 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"); + 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; diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index 7d514839c764..f01c1fae4d84 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -293,6 +293,25 @@ enum max14577_charger_reg { #define MAX77836_CHARGER_CURRENT_LIMIT_HIGH_STEP 25000U #define MAX77836_CHARGER_CURRENT_LIMIT_MAX 475000U +/* + * MAX14577 charger End-Of-Charge current limits + * (as in CHGCTRL5 register), uA + */ +#define MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN 50000U +#define MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP 10000U +#define MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX 200000U + +/* + * MAX14577/MAX77836 Battery Constant Voltage + * (as in CHGCTRL3 register), uV + */ +#define MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN 4000000U +#define MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP 20000U +#define MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX 4350000U + +/* Default value for fast charge timer, in hours */ +#define MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT 5 + /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000 diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h index 3c098d57b1d1..ccfaf952c31b 100644 --- a/include/linux/mfd/max14577.h +++ b/include/linux/mfd/max14577.h @@ -54,6 +54,13 @@ struct max14577_regulator_platform_data { struct device_node *of_node; }; +struct max14577_charger_platform_data { + u32 constant_uvolt; + u32 fast_charge_uamp; + u32 eoc_uamp; + u32 ovp_uvolt; +}; + /* * MAX14577 MFD platform data */ From 2c33e9296202cd11bf2e2f801b69ffba0953748a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:58 +0200 Subject: [PATCH 07/12] power: max17040: Add ID for MAX77836 Fuel Gauge block MAX77836 has the same Fuel Gauge as MAX17040/17048. The max17040 driver can be safely re-used. The patch adds MAX77836 device to the array of i2c_device_id. Additionally it removes the id associated with MAX17040 device as the value is not used. Signed-off-by: Krzysztof Kozlowski Acked-by: Andrew Morton Signed-off-by: Lee Jones --- drivers/power/max17040_battery.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 0fbac861080d..14d44706327b 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); From 8d70d68d7a1b3082ca5a3808be18103a83ae348d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:53:59 +0200 Subject: [PATCH 08/12] devicetree: mfd: max14577: Add device tree bindings document Add document describing device tree bindings for MAX14577 MFD drivers: MFD core, extcon, regulator and charger. Both MAX14577 and MAX77836 chipsets are documented. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Tomasz Figa Acked-by: Andrew Morton Signed-off-by: Lee Jones --- .../devicetree/bindings/mfd/max14577.txt | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 Documentation/devicetree/bindings/mfd/max14577.txt diff --git a/Documentation/devicetree/bindings/mfd/max14577.txt b/Documentation/devicetree/bindings/mfd/max14577.txt new file mode 100644 index 000000000000..236264c10b92 --- /dev/null +++ b/Documentation/devicetree/bindings/mfd/max14577.txt @@ -0,0 +1,146 @@ +Maxim MAX14577/77836 Multi-Function Device + +MAX14577 is a Multi-Function Device with Micro-USB Interface Circuit, Li+ +Battery Charger and SFOUT LDO output for powering USB devices. It is +interfaced to host controller using I2C. + +MAX77836 additionally contains PMIC (with two LDO regulators) and Fuel Gauge. + + +Required properties: +- compatible : Must be "maxim,max14577" or "maxim,max77836". +- reg : I2C slave address for the max14577 chip (0x25 for max14577/max77836) +- interrupts : IRQ line for the chip. +- interrupt-parent : The parent interrupt controller. + + +Required nodes: + - charger : + Node for configuring the charger driver. + Required properties: + - compatible : "maxim,max14577-charger" + or "maxim,max77836-charger" + - maxim,fast-charge-uamp : Current in uA for Fast Charge; + Valid values: + - for max14577: 90000 - 950000; + - for max77836: 45000 - 475000; + - maxim,eoc-uamp : Current in uA for End-Of-Charge mode; + Valid values: + - for max14577: 50000 - 200000; + - for max77836: 5000 - 100000; + - maxim,ovp-uvolt : OverVoltage Protection Threshold in uV; + In an overvoltage condition, INT asserts and charging + stops. Valid values: + - 6000000, 6500000, 7000000, 7500000; + - maxim,constant-uvolt : Battery Constant Voltage in uV; + Valid values: + - 4000000 - 4280000 (step by 20000); + - 4350000; + + +Optional nodes: +- max14577-muic/max77836-muic : + Node used only by extcon consumers. + Required properties: + - compatible : "maxim,max14577-muic" or "maxim,max77836-muic" + +- regulators : + Required properties: + - compatible : "maxim,max14577-regulator" + or "maxim,max77836-regulator" + + May contain a sub-node per regulator from the list below. Each + sub-node should contain the constraints and initialization information + for that regulator. See regulator.txt for a description of standard + properties for these sub-nodes. + + List of valid regulator names: + - for max14577: CHARGER, SAFEOUT. + - for max77836: CHARGER, SAFEOUT, LDO1, LDO2. + + The SAFEOUT is a fixed voltage regulator so there is no need to specify + voltages for it. + + +Example: + +#include + +max14577@25 { + compatible = "maxim,max14577"; + reg = <0x25>; + interrupt-parent = <&gpx1>; + interrupts = <5 IRQ_TYPE_NONE>; + + muic: max14577-muic { + compatible = "maxim,max14577-muic"; + }; + + regulators { + compatible = "maxim,max14577-regulator"; + + SAFEOUT { + regulator-name = "SAFEOUT"; + }; + CHARGER { + regulator-name = "CHARGER"; + regulator-min-microamp = <90000>; + regulator-max-microamp = <950000>; + regulator-boot-on; + }; + }; + + charger { + compatible = "maxim,max14577-charger"; + + maxim,constant-uvolt = <4350000>; + maxim,fast-charge-uamp = <450000>; + maxim,eoc-uamp = <50000>; + maxim,ovp-uvolt = <6500000>; + }; +}; + + +max77836@25 { + compatible = "maxim,max77836"; + reg = <0x25>; + interrupt-parent = <&gpx1>; + interrupts = <5 IRQ_TYPE_NONE>; + + muic: max77836-muic { + compatible = "maxim,max77836-muic"; + }; + + regulators { + compatible = "maxim,max77836-regulator"; + + SAFEOUT { + regulator-name = "SAFEOUT"; + }; + CHARGER { + regulator-name = "CHARGER"; + regulator-min-microamp = <90000>; + regulator-max-microamp = <950000>; + regulator-boot-on; + }; + LDO1 { + regulator-name = "LDO1"; + regulator-min-microvolt = <2700000>; + regulator-max-microvolt = <2700000>; + }; + LDO2 { + regulator-name = "LDO2"; + regulator-min-microvolt = <800000>; + regulator-max-microvolt = <3950000>; + }; + }; + + charger { + compatible = "maxim,max77836-charger"; + + maxim,constant-uvolt = <4350000>; + maxim,fast-charge-uamp = <225000>; + maxim,eoc-uamp = <7500>; + maxim,ovp-uvolt = <6500000>; + }; +}; From d6cc1f5824cbca392d099f3bb0c441efd9e54de9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 12 Sep 2014 08:54:00 +0200 Subject: [PATCH 09/12] Documentation: charger: max14577: Document exported sysfs entry Document the 'fast charge timer' setting exported by max14577 driver through sysfs entry. Signed-off-by: Krzysztof Kozlowski Acked-by: Andrew Morton Signed-off-by: Lee Jones --- Documentation/ABI/testing/sysfs-class-power | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 78c7baca3587..ad4fb90ab23d 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -18,3 +18,17 @@ Description: This file is writeable and can be used to set the assumed battery 'full level'. As batteries age, this value has to be amended over time. + +What: /sys/class/power_supply/max14577-charger/device/fast_charge_timer +Date: July 2014 +KernelVersion: 3.18.0 +Contact: Krzysztof Kozlowski +Description: + This entry shows and sets the maximum time the max14577 + charger operates in fast-charge mode. When the timer expires + the device will terminate fast-charge mode (charging current + will drop to 0 A) and will trigger interrupt. + + Valid values: + - 5, 6 or 7 (hours), + - 0: disabled. From 5033263992eece84e19946d2cab940c86ec862ba Mon Sep 17 00:00:00 2001 From: Andreas Werner Date: Wed, 27 Aug 2014 19:52:06 +0200 Subject: [PATCH 10/12] watchdog: menf21bmc_wdt: Introduce MEN 14F021P00 BMC Watchdog driver Added driver to support the 14F021P00 BMC Watchdog. The BMC is a Board Management Controller including watchdog functionality. Signed-off-by: Andreas Werner Reviewed-by: Guenter Roeck Signed-off-by: Lee Jones --- drivers/watchdog/Kconfig | 10 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/menf21bmc_wdt.c | 203 +++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+) create mode 100644 drivers/watchdog/menf21bmc_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index f57312fced80..a50828e34e3b 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 +#include +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_wdt"); From 38433639af915deeb0b0e28462dd740ce57b72fd Mon Sep 17 00:00:00 2001 From: Andreas Werner Date: Wed, 27 Aug 2014 19:52:36 +0200 Subject: [PATCH 11/12] leds: leds-menf21bmc: Introduce MEN 14F021P00 BMC LED driver Added driver to support the 14F021P00 BMC LEDs. The BMC is a Board Management Controller including four LEDs which can be switched on and off. Signed-off-by: Andreas Werner Acked-by: Bryan Wu Signed-off-by: Lee Jones --- drivers/leds/Kconfig | 9 +++ drivers/leds/Makefile | 1 + drivers/leds/leds-menf21bmc.c | 131 ++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/leds/leds-menf21bmc.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 8c96e2ddf43b..eadd56c91551 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 d8cc5f2777de..9a72fddc1d78 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 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 +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("MEN 14F021P00 BMC led driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_led"); From 964356938fcd3c0001a786f55b9f0a0fbe47656a Mon Sep 17 00:00:00 2001 From: Andreas Werner Date: Wed, 27 Aug 2014 19:53:06 +0200 Subject: [PATCH 12/12] hwmon: (menf21bmc) Introduce MEN14F021P00 BMC HWMON driver Added driver to support the 14F021P00 BMC Hardware Monitoring. The BMC is a Board Management Controller including monitoring of the board voltages. Signed-off-by: Andreas Werner Reviewed-by: Guenter Roeck Signed-off-by: Lee Jones --- Documentation/hwmon/menf21bmc | 50 +++++++ drivers/hwmon/Kconfig | 10 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/menf21bmc_hwmon.c | 230 ++++++++++++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 Documentation/hwmon/menf21bmc create mode 100644 drivers/hwmon/menf21bmc_hwmon.c diff --git a/Documentation/hwmon/menf21bmc b/Documentation/hwmon/menf21bmc new file mode 100644 index 000000000000..2a273a065c5e --- /dev/null +++ b/Documentation/hwmon/menf21bmc @@ -0,0 +1,50 @@ +Kernel driver menf21bmc_hwmon +============================= + +Supported chips: + * MEN 14F021P00 + Prefix: 'menf21bmc_hwmon' + Adresses scanned: - + +Author: Andreas Werner + +Description +----------- + +The menf21bmc is a Board Management Controller (BMC) which provides an I2C +interface to the host to access the features implemented in the BMC. + +This driver gives access to the voltage monitoring feature of the main +voltages of the board. +The voltage sensors are connected to the ADC inputs of the BMC which is +a PIC16F917 Mikrocontroller. + +Usage Notes +----------- + +This driver is part of the MFD driver named "menf21bmc" and does +not auto-detect devices. +You will have to instantiate the MFD driver explicitly. +Please see Documentation/i2c/instantiating-devices for +details. + +Sysfs entries +------------- + +The following attributes are supported. All attributes are read only +The Limits are read once by the driver. + +in0_input +3.3V input voltage +in1_input +5.0V input voltage +in2_input +12.0V input voltage +in3_input +5V Standby input voltage +in4_input VBAT (on board battery) + +in[0-4]_min Minimum voltage limit +in[0-4]_max Maximum voltage limit + +in0_label "MON_3_3V" +in1_label "MON_5V" +in2_label "MON_12V" +in3_label "5V_STANDBY" +in4_label "VBAT" diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f00d048aa583..c08d172bbab4 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 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/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 +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("MEN 14F021P00 BMC hwmon"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:menf21bmc_hwmon");