From f63d32d4080407176082ef2cc439a3c81b15af16 Mon Sep 17 00:00:00 2001 From: Kishon Vijay Abraham I Date: Fri, 28 Mar 2014 12:21:02 +0530 Subject: extcon: palmas: explicitly set edev name as node name commit ca488 (extcon: of: Remove unnecessary function call by using the name of device_node) started using node name instead of device name to get the extcon device. This breaks dwc3-omap since it's not able to get the extcon device anymore. Fixed it by setting edev name of palmas to be the same as its node name. Signed-off-by: Kishon Vijay Abraham I Reported-by: Felipe Balbi Reviewed-by: Felipe Balbi Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-palmas.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index ddff2b72f0a8..51db5bcef720 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -188,11 +189,13 @@ static int palmas_usb_probe(struct platform_device *pdev) palmas_usb->edev.supported_cable = palmas_extcon_cable; palmas_usb->edev.dev.parent = palmas_usb->dev; + palmas_usb->edev.name = kstrdup(node->name, GFP_KERNEL); palmas_usb->edev.mutually_exclusive = mutually_exclusive; status = extcon_dev_register(&palmas_usb->edev); if (status) { dev_err(&pdev->dev, "failed to register extcon device\n"); + kfree(palmas_usb->edev.name); return status; } @@ -230,6 +233,7 @@ static int palmas_usb_probe(struct platform_device *pdev) fail_extcon: extcon_dev_unregister(&palmas_usb->edev); + kfree(palmas_usb->edev.name); return status; } @@ -239,6 +243,7 @@ static int palmas_usb_remove(struct platform_device *pdev) struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); extcon_dev_unregister(&palmas_usb->edev); + kfree(palmas_usb->edev.name); return 0; } -- cgit v1.2.3 From 4005da5ce8cd21e2566a3cb51f74be9acdc457a9 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 14 Apr 2014 11:17:12 +0200 Subject: extcon: max14577: Change extcon name instead of static name according to device type This patch use device name to make sysfs path according to device type: max14577-muic - /sys/class/extcon/max14577-muic/ max77836-muic - /sys/class/extcon/max77836-muic/ Signed-off-by: Krzysztof Kozlowski Acked-by: Chanwoo Choi Tested-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/extcon/extcon-max14577.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 3846941801b8..59f084fee4f3 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -24,7 +24,6 @@ #include #include -#define DEV_NAME "max14577-muic" #define DELAY_MS_DEFAULT 17000 /* unit: millisecond */ enum max14577_muic_adc_debounce_time { @@ -673,7 +672,8 @@ static int max14577_muic_probe(struct platform_device *pdev) dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); return -ENOMEM; } - info->edev->name = DEV_NAME; + + info->edev->name = dev_name(&pdev->dev); info->edev->supported_cable = max14577_extcon_cable; ret = extcon_dev_register(info->edev); if (ret) { @@ -737,7 +737,7 @@ static int max14577_muic_remove(struct platform_device *pdev) static struct platform_driver max14577_muic_driver = { .driver = { - .name = DEV_NAME, + .name = "max14577-muic", .owner = THIS_MODULE, }, .probe = max14577_muic_probe, -- cgit v1.2.3 From 575343d161d75dc1516f53436b9eb47d04eda938 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:13 +0200 Subject: mfd: max14577: Add muic prefix to regmap config Add muic prefix to regmap config to differentiate between another regmap config for MAX77836 PMIC node. Additionally remove unused symbols: MAX14577_REG_INVALID and max14577_irq_source. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Lee Jones --- drivers/mfd/max14577.c | 9 +++++---- include/linux/mfd/max14577-private.h | 4 +--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 5f13cefe8def..d180fae8e317 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -37,7 +37,7 @@ static struct mfd_cell max14577_devs[] = { { .name = "max14577-charger", }, }; -static bool max14577_volatile_reg(struct device *dev, unsigned int reg) +static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { case MAX14577_REG_INT1 ... MAX14577_REG_STATUS3: @@ -48,10 +48,10 @@ static bool max14577_volatile_reg(struct device *dev, unsigned int reg) return false; } -static const struct regmap_config max14577_regmap_config = { +static const struct regmap_config max14577_muic_regmap_config = { .reg_bits = 8, .val_bits = 8, - .volatile_reg = max14577_volatile_reg, + .volatile_reg = max14577_muic_volatile_reg, .max_register = MAX14577_REG_END, }; @@ -113,7 +113,8 @@ static int max14577_i2c_probe(struct i2c_client *i2c, max14577->i2c = i2c; max14577->irq = i2c->irq; - max14577->regmap = devm_regmap_init_i2c(i2c, &max14577_regmap_config); + max14577->regmap = devm_regmap_init_i2c(i2c, + &max14577_muic_regmap_config); if (IS_ERR(max14577->regmap)) { ret = PTR_ERR(max14577->regmap); dev_err(max14577->dev, "Failed to allocate register map: %d\n", diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index c9b332fb0d5d..97b78d94f92f 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -22,9 +22,7 @@ #include #include -#define MAX14577_REG_INVALID (0xff) - -/* Slave addr = 0x4A: Interrupt */ +/* Slave addr = 0x4A: MUIC and Charger */ enum max14577_reg { MAX14577_REG_DEVICEID = 0x00, MAX14577_REG_INT1 = 0x01, -- cgit v1.2.3 From eccb80cc22354a12255c2579247a92a30a4c881b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:14 +0200 Subject: mfd: max14577: Add detection of device type This patch continues the preparation for adding support for MAX77836 device to existing max14577 driver. Add enum for types of devices supported by this driver. The device type will be detected by matching of_device_id, or i2c_device_id as a fallback. The patch also moves to separate function the code related to displaying DeviceID register values. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Lee Jones --- drivers/mfd/max14577.c | 64 +++++++++++++++++++++++++----------- include/linux/mfd/max14577-private.h | 12 ++++--- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index d180fae8e317..0e07ed74ab41 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,14 @@ static struct mfd_cell max14577_devs[] = { { .name = "max14577-charger", }, }; +static struct of_device_id max14577_dt_match[] = { + { + .compatible = "maxim,max14577", + .data = (void *)MAXIM_DEVICE_TYPE_MAX14577, + }, + {}, +}; + static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -83,13 +92,34 @@ static const struct regmap_irq_chip max14577_irq_chip = { .num_irqs = ARRAY_SIZE(max14577_irqs), }; +static void max14577_print_dev_type(struct max14577 *max14577) +{ + u8 reg_data, vendor_id, device_id; + int ret; + + ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, + ®_data); + if (ret) { + dev_err(max14577->dev, + "Failed to read DEVICEID register: %d\n", ret); + return; + } + + vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> + DEVID_VENDORID_SHIFT); + device_id = ((reg_data & DEVID_DEVICEID_MASK) >> + DEVID_DEVICEID_SHIFT); + + dev_info(max14577->dev, "Device type: %u (ID: 0x%x, vendor: 0x%x)\n", + max14577->dev_type, device_id, vendor_id); +} + static int max14577_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct max14577 *max14577; struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); struct device_node *np = i2c->dev.of_node; - u8 reg_data; int ret = 0; if (np) { @@ -122,19 +152,17 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return ret; } - ret = max14577_read_reg(max14577->regmap, MAX14577_REG_DEVICEID, - ®_data); - if (ret) { - dev_err(max14577->dev, "Device not found on this channel: %d\n", - ret); - return ret; + if (np) { + const struct of_device_id *of_id; + + of_id = of_match_device(max14577_dt_match, &i2c->dev); + if (of_id) + max14577->dev_type = (unsigned int)of_id->data; + } else { + max14577->dev_type = id->driver_data; } - max14577->vendor_id = ((reg_data & DEVID_VENDORID_MASK) >> - DEVID_VENDORID_SHIFT); - max14577->device_id = ((reg_data & DEVID_DEVICEID_MASK) >> - DEVID_DEVICEID_SHIFT); - dev_info(max14577->dev, "Device ID: 0x%x, vendor: 0x%x\n", - max14577->device_id, max14577->vendor_id); + + max14577_print_dev_type(max14577); ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, @@ -173,7 +201,7 @@ static int max14577_i2c_remove(struct i2c_client *i2c) } static const struct i2c_device_id max14577_i2c_id[] = { - { "max14577", 0 }, + { "max14577", MAXIM_DEVICE_TYPE_MAX14577, }, { } }; MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); @@ -216,11 +244,6 @@ static int max14577_resume(struct device *dev) } #endif /* CONFIG_PM_SLEEP */ -static struct of_device_id max14577_dt_match[] = { - { .compatible = "maxim,max14577", }, - {}, -}; - static SIMPLE_DEV_PM_OPS(max14577_pm, max14577_suspend, max14577_resume); static struct i2c_driver max14577_i2c_driver = { @@ -237,6 +260,9 @@ static struct i2c_driver max14577_i2c_driver = { 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); + return i2c_add_driver(&max14577_i2c_driver); } subsys_initcall(max14577_i2c_init); diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index 97b78d94f92f..1ce6f2952cc9 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -22,6 +22,13 @@ #include #include +enum maxim_device_type { + MAXIM_DEVICE_TYPE_UNKNOWN = 0, + MAXIM_DEVICE_TYPE_MAX14577, + + MAXIM_DEVICE_TYPE_NUM, +}; + /* Slave addr = 0x4A: MUIC and Charger */ enum max14577_reg { MAX14577_REG_DEVICEID = 0x00, @@ -271,15 +278,12 @@ enum max14577_irq { struct max14577 { struct device *dev; struct i2c_client *i2c; /* Slave addr = 0x4A */ + enum maxim_device_type dev_type; struct regmap *regmap; struct regmap_irq_chip_data *irq_data; int irq; - - /* Device ID */ - u8 vendor_id; /* Vendor Identification */ - u8 device_id; /* Chip Version */ }; /* MAX14577 shared regmap API function */ -- cgit v1.2.3 From 2b1d18f181b8712564204506c1bd1a1e335b689f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:15 +0200 Subject: extcon: max14577: Add max14577 prefix to muic_irqs Add max14577 prefix to muic_irqs array. This prepares for max77836 support in this extcon driver. Signed-off-by: Krzysztof Kozlowski Acked-by: Chanwoo Choi Tested-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/extcon/extcon-max14577.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 59f084fee4f3..6f921b7c3e5b 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -85,7 +85,7 @@ struct max14577_muic_irq { unsigned int virq; }; -static struct max14577_muic_irq muic_irqs[] = { +static struct max14577_muic_irq max14577_muic_irqs[] = { { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, @@ -538,9 +538,9 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) * However we only need to know whether it was ADC, charger * or both interrupts so decode IRQ and turn on proper flags. */ - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) - if (irq == muic_irqs[i].virq) - irq_type = muic_irqs[i].irq; + for (i = 0; i < ARRAY_SIZE(max14577_muic_irqs); i++) + if (irq == max14577_muic_irqs[i].virq) + irq_type = max14577_muic_irqs[i].irq; switch (irq_type) { case MAX14577_IRQ_INT1_ADC: @@ -644,8 +644,8 @@ static int max14577_muic_probe(struct platform_device *pdev) INIT_WORK(&info->irq_work, max14577_muic_irq_work); /* Support irq domain for max14577 MUIC device */ - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { - struct max14577_muic_irq *muic_irq = &muic_irqs[i]; + for (i = 0; i < ARRAY_SIZE(max14577_muic_irqs); i++) { + struct max14577_muic_irq *muic_irq = &max14577_muic_irqs[i]; unsigned int virq = 0; virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq); -- cgit v1.2.3 From 0ca852b794dabb3a4dd5c38223cb49a07015540b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:16 +0200 Subject: extcon: max14577: Choose muic_irqs according to device type This patch continues the preparation for adding support for max77836 device to existing max14577 driver. During probe choose muic_irqs according to device type. Currently there are only "max14577_muic_irqs" but later patch will add max77836 interrupts. Signed-off-by: Krzysztof Kozlowski Acked-by: Chanwoo Choi Tested-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/extcon/extcon-max14577.c | 65 +++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 6f921b7c3e5b..1513013a92f1 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -39,6 +39,29 @@ enum max14577_muic_status { MAX14577_MUIC_STATUS_END, }; +/** + * struct max14577_muic_irq + * @irq: the index of irq list of MUIC device. + * @name: the name of irq. + * @virq: the virtual irq to use irq domain + */ +struct max14577_muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +static struct max14577_muic_irq max14577_muic_irqs[] = { + { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, + { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, + { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, + { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, + { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, + { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, +}; + struct max14577_muic_info { struct device *dev; struct max14577 *max14577; @@ -47,6 +70,8 @@ struct max14577_muic_info { int prev_chg_type; u8 status[MAX14577_MUIC_STATUS_END]; + struct max14577_muic_irq *muic_irqs; + unsigned int muic_irqs_num; bool irq_adc; bool irq_chg; struct work_struct irq_work; @@ -73,29 +98,6 @@ enum max14577_muic_cable_group { MAX14577_CABLE_GROUP_CHG, }; -/** - * struct max14577_muic_irq - * @irq: the index of irq list of MUIC device. - * @name: the name of irq. - * @virq: the virtual irq to use irq domain - */ -struct max14577_muic_irq { - unsigned int irq; - const char *name; - unsigned int virq; -}; - -static struct max14577_muic_irq max14577_muic_irqs[] = { - { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, - { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, - { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, - { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, - { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, - { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, - { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, - { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, -}; - /* Define supported accessory type */ enum max14577_muic_acc_type { MAX14577_MUIC_ADC_GROUND = 0x0, @@ -538,9 +540,9 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) * However we only need to know whether it was ADC, charger * or both interrupts so decode IRQ and turn on proper flags. */ - for (i = 0; i < ARRAY_SIZE(max14577_muic_irqs); i++) - if (irq == max14577_muic_irqs[i].virq) - irq_type = max14577_muic_irqs[i].irq; + for (i = 0; i < info->muic_irqs_num; i++) + if (irq == info->muic_irqs[i].virq) + irq_type = info->muic_irqs[i].irq; switch (irq_type) { case MAX14577_IRQ_INT1_ADC: @@ -643,9 +645,16 @@ static int max14577_muic_probe(struct platform_device *pdev) INIT_WORK(&info->irq_work, max14577_muic_irq_work); + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX14577: + default: + info->muic_irqs = max14577_muic_irqs; + info->muic_irqs_num = ARRAY_SIZE(max14577_muic_irqs); + } + /* Support irq domain for max14577 MUIC device */ - for (i = 0; i < ARRAY_SIZE(max14577_muic_irqs); i++) { - struct max14577_muic_irq *muic_irq = &max14577_muic_irqs[i]; + for (i = 0; i < info->muic_irqs_num; i++) { + struct max14577_muic_irq *muic_irq = &info->muic_irqs[i]; unsigned int virq = 0; virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq); -- cgit v1.2.3 From c7846852ec8f304c629963202fa565452e8fe34c Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:17 +0200 Subject: mfd: max14577: Add MAX14577 prefix to IRQ defines This patch prepares for adding support for MAX77836 device to existing max14577 driver by adding MAX14577 prefix to defines of interrupts. This is only a rename-like patch, new code is not added. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Lee Jones --- drivers/mfd/max14577.c | 24 ++++++++++++------------ include/linux/mfd/max14577-private.h | 28 ++++++++++++++-------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 0e07ed74ab41..6f39dec9dfdf 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -66,20 +66,20 @@ static const struct regmap_config max14577_muic_regmap_config = { static const struct regmap_irq max14577_irqs[] = { /* INT1 interrupts */ - { .reg_offset = 0, .mask = INT1_ADC_MASK, }, - { .reg_offset = 0, .mask = INT1_ADCLOW_MASK, }, - { .reg_offset = 0, .mask = INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, /* INT2 interrupts */ - { .reg_offset = 1, .mask = INT2_CHGTYP_MASK, }, - { .reg_offset = 1, .mask = INT2_CHGDETRUN_MASK, }, - { .reg_offset = 1, .mask = INT2_DCDTMR_MASK, }, - { .reg_offset = 1, .mask = INT2_DBCHG_MASK, }, - { .reg_offset = 1, .mask = INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, /* INT3 interrupts */ - { .reg_offset = 2, .mask = INT3_EOC_MASK, }, - { .reg_offset = 2, .mask = INT3_CGMBC_MASK, }, - { .reg_offset = 2, .mask = INT3_OVP_MASK, }, - { .reg_offset = 2, .mask = INT3_MBCCHGERR_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, }; static const struct regmap_irq_chip max14577_irq_chip = { diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index 1ce6f2952cc9..989183d232cd 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -79,20 +79,20 @@ enum max14577_muic_charger_type { }; /* MAX14577 interrupts */ -#define INT1_ADC_MASK (0x1 << 0) -#define INT1_ADCLOW_MASK (0x1 << 1) -#define INT1_ADCERR_MASK (0x1 << 2) - -#define INT2_CHGTYP_MASK (0x1 << 0) -#define INT2_CHGDETRUN_MASK (0x1 << 1) -#define INT2_DCDTMR_MASK (0x1 << 2) -#define INT2_DBCHG_MASK (0x1 << 3) -#define INT2_VBVOLT_MASK (0x1 << 4) - -#define INT3_EOC_MASK (0x1 << 0) -#define INT3_CGMBC_MASK (0x1 << 1) -#define INT3_OVP_MASK (0x1 << 2) -#define INT3_MBCCHGERR_MASK (0x1 << 3) +#define MAX14577_INT1_ADC_MASK BIT(0) +#define MAX14577_INT1_ADCLOW_MASK BIT(1) +#define MAX14577_INT1_ADCERR_MASK BIT(2) + +#define MAX14577_INT2_CHGTYP_MASK BIT(0) +#define MAX14577_INT2_CHGDETRUN_MASK BIT(1) +#define MAX14577_INT2_DCDTMR_MASK BIT(2) +#define MAX14577_INT2_DBCHG_MASK BIT(3) +#define MAX14577_INT2_VBVOLT_MASK BIT(4) + +#define MAX14577_INT3_EOC_MASK BIT(0) +#define MAX14577_INT3_CGMBC_MASK BIT(1) +#define MAX14577_INT3_OVP_MASK BIT(2) +#define MAX14577_INT3_MBCCHGERR_MASK BIT(3) /* MAX14577 DEVICE ID register */ #define DEVID_VENDORID_SHIFT 0 -- cgit v1.2.3 From aee2a57c7482c712052b877218aa2c5bc0fe8626 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:18 +0200 Subject: mfd: max77836: Add MAX77836 support to max14577 driver Add Maxim 77836 support to max14577 driver. The chipsets have same MUIC component so the extcon, charger and regulators are almost the same. The MAX77836 however has also PMIC and Fuel Gauge. The MAX77836 uses three I2C slave addresses and has additional interrupts (related to PMIC and Fuel Gauge). It has also Interrupt Source register, just like MAX77686 and MAX77693. The MAX77836 PMIC's TOPSYS and INTSRC interrupts are reported in the PMIC block. The PMIC block has different I2C slave address and uses own regmap so another regmap_irq_chip is needed. Since we have two regmap_irq_chip, use shared interrupts on MAX77836. This patch adds additional defines and functions to the max14577 MFD core driver so the driver will handle both chipsets. Also this patch replaces "0x1 << N" with BIT(N) in defines for register masks. Signed-off-by: Krzysztof Kozlowski Acked-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/mfd/Kconfig | 6 +- drivers/mfd/max14577.c | 217 +++++++++++++++++++++++++++++++++-- include/linux/mfd/max14577-private.h | 145 +++++++++++++++++------ include/linux/mfd/max14577.h | 7 +- 4 files changed, 330 insertions(+), 45 deletions(-) diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 33834120d057..5bdefe72625e 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -331,15 +331,15 @@ config MFD_88PM860X battery-charger under the corresponding menus. config MFD_MAX14577 - bool "Maxim Semiconductor MAX14577 MUIC + Charger Support" + bool "Maxim Semiconductor MAX14577/77836 MUIC + Charger Support" depends on I2C=y select MFD_CORE select REGMAP_I2C select REGMAP_IRQ select IRQ_DOMAIN help - Say yes here to add support for Maxim Semiconductor MAX14577. - This is a Micro-USB IC with Charger controls on chip. + Say yes here to add support for Maxim Semiconductor MAX14577 and + MAX77836 Micro-USB ICs with battery charger. This driver provides common support for accessing the device; additional drivers must be enabled in order to use the functionality of the device. diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 6f39dec9dfdf..20e3b2d81bf0 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -1,7 +1,7 @@ /* - * max14577.c - mfd core driver for the Maxim 14577 + * max14577.c - mfd core driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi * Krzysztof Kozlowski * @@ -38,11 +38,34 @@ static struct mfd_cell max14577_devs[] = { { .name = "max14577-charger", }, }; +static struct mfd_cell max77836_devs[] = { + { + .name = "max77836-muic", + .of_compatible = "maxim,max77836-muic", + }, + { + .name = "max77836-regulator", + .of_compatible = "maxim,max77836-regulator", + }, + { + .name = "max77836-charger", + .of_compatible = "maxim,max77836-charger", + }, + { + .name = "max77836-battery", + .of_compatible = "maxim,max77836-battery", + }, +}; + static struct of_device_id max14577_dt_match[] = { { .compatible = "maxim,max14577", .data = (void *)MAXIM_DEVICE_TYPE_MAX14577, }, + { + .compatible = "maxim,max77836", + .data = (void *)MAXIM_DEVICE_TYPE_MAX77836, + }, {}, }; @@ -57,6 +80,26 @@ static bool max14577_muic_volatile_reg(struct device *dev, unsigned int reg) return false; } +static bool max77836_muic_volatile_reg(struct device *dev, unsigned int reg) +{ + /* Any max14577 volatile registers are also max77836 volatile. */ + if (max14577_muic_volatile_reg(dev, reg)) + return true; + + switch (reg) { + case MAX77836_FG_REG_VCELL_MSB ... MAX77836_FG_REG_SOC_LSB: + case MAX77836_FG_REG_CRATE_MSB ... MAX77836_FG_REG_CRATE_LSB: + case MAX77836_FG_REG_STATUS_H ... MAX77836_FG_REG_STATUS_L: + case MAX77836_PMIC_REG_INTSRC: + case MAX77836_PMIC_REG_TOPSYS_INT: + case MAX77836_PMIC_REG_TOPSYS_STAT: + return true; + default: + break; + } + return false; +} + static const struct regmap_config max14577_muic_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -64,6 +107,13 @@ static const struct regmap_config max14577_muic_regmap_config = { .max_register = MAX14577_REG_END, }; +static const struct regmap_config max77836_pmic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = max77836_muic_volatile_reg, + .max_register = MAX77836_PMIC_REG_END, +}; + static const struct regmap_irq max14577_irqs[] = { /* INT1 interrupts */ { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, @@ -86,12 +136,56 @@ static const struct regmap_irq_chip max14577_irq_chip = { .name = "max14577", .status_base = MAX14577_REG_INT1, .mask_base = MAX14577_REG_INTMASK1, - .mask_invert = 1, + .mask_invert = true, .num_regs = 3, .irqs = max14577_irqs, .num_irqs = ARRAY_SIZE(max14577_irqs), }; +static const struct regmap_irq max77836_muic_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, + { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, + /* INT2 interrupts */ + { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DCDTMR_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_DBCHG_MASK, }, + { .reg_offset = 1, .mask = MAX14577_INT2_VBVOLT_MASK, }, + { .reg_offset = 1, .mask = MAX77836_INT2_VIDRM_MASK, }, + /* INT3 interrupts */ + { .reg_offset = 2, .mask = MAX14577_INT3_EOC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_CGMBC_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_OVP_MASK, }, + { .reg_offset = 2, .mask = MAX14577_INT3_MBCCHGERR_MASK, }, +}; + +static const struct regmap_irq_chip max77836_muic_irq_chip = { + .name = "max77836-muic", + .status_base = MAX14577_REG_INT1, + .mask_base = MAX14577_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77836_muic_irqs, + .num_irqs = ARRAY_SIZE(max77836_muic_irqs), +}; + +static const struct regmap_irq max77836_pmic_irqs[] = { + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T120C_MASK, }, + { .reg_offset = 0, .mask = MAX77836_TOPSYS_INT_T140C_MASK, }, +}; + +static const struct regmap_irq_chip max77836_pmic_irq_chip = { + .name = "max77836-pmic", + .status_base = MAX77836_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77836_PMIC_REG_TOPSYS_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77836_pmic_irqs, + .num_irqs = ARRAY_SIZE(max77836_pmic_irqs), +}; + static void max14577_print_dev_type(struct max14577 *max14577) { u8 reg_data, vendor_id, device_id; @@ -114,6 +208,81 @@ static void max14577_print_dev_type(struct max14577 *max14577) max14577->dev_type, device_id, vendor_id); } +/* + * Max77836 specific initialization code for driver probe. + * Adds new I2C dummy device, regmap and regmap IRQ chip. + * Unmasks Interrupt Source register. + * + * On success returns 0. + * On failure returns errno and reverts any changes done so far (e.g. remove + * I2C dummy device), except masking the INT SRC register. + */ +static int max77836_init(struct max14577 *max14577) +{ + int ret; + u8 intsrc_mask; + + max14577->i2c_pmic = i2c_new_dummy(max14577->i2c->adapter, + I2C_ADDR_PMIC); + if (!max14577->i2c_pmic) { + dev_err(max14577->dev, "Failed to register PMIC I2C device\n"); + return -ENODEV; + } + i2c_set_clientdata(max14577->i2c_pmic, max14577); + + max14577->regmap_pmic = devm_regmap_init_i2c(max14577->i2c_pmic, + &max77836_pmic_regmap_config); + if (IS_ERR(max14577->regmap_pmic)) { + ret = PTR_ERR(max14577->regmap_pmic); + dev_err(max14577->dev, "Failed to allocate PMIC register map: %d\n", + ret); + goto err; + } + + /* Un-mask MAX77836 Interrupt Source register */ + ret = max14577_read_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, &intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to read PMIC register\n"); + goto err; + } + + intsrc_mask &= ~(MAX77836_INTSRC_MASK_TOP_INT_MASK); + intsrc_mask &= ~(MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK); + ret = max14577_write_reg(max14577->regmap_pmic, + MAX77836_PMIC_REG_INTSRC_MASK, intsrc_mask); + if (ret < 0) { + dev_err(max14577->dev, "Failed to write PMIC register\n"); + goto err; + } + + ret = regmap_add_irq_chip(max14577->regmap_pmic, max14577->irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED, + 0, &max77836_pmic_irq_chip, + &max14577->irq_data_pmic); + if (ret != 0) { + dev_err(max14577->dev, "Failed to request PMIC IRQ %d: %d\n", + max14577->irq, ret); + goto err; + } + + return 0; + +err: + i2c_unregister_device(max14577->i2c_pmic); + + return ret; +} + +/* + * Max77836 specific de-initialization code for driver remove. + */ +static void max77836_remove(struct max14577 *max14577) +{ + regmap_del_irq_chip(max14577->irq, max14577->irq_data_pmic); + i2c_unregister_device(max14577->i2c_pmic); +} + static int max14577_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -121,6 +290,10 @@ static int max14577_i2c_probe(struct i2c_client *i2c, struct max14577_platform_data *pdata = dev_get_platdata(&i2c->dev); struct device_node *np = i2c->dev.of_node; int ret = 0; + const struct regmap_irq_chip *irq_chip; + struct mfd_cell *mfd_devs; + unsigned int mfd_devs_size; + int irq_flags; if (np) { pdata = devm_kzalloc(&i2c->dev, sizeof(*pdata), GFP_KERNEL); @@ -164,9 +337,24 @@ static int max14577_i2c_probe(struct i2c_client *i2c, max14577_print_dev_type(max14577); + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_chip = &max77836_muic_irq_chip; + mfd_devs = max77836_devs; + mfd_devs_size = ARRAY_SIZE(max77836_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + irq_chip = &max14577_irq_chip; + mfd_devs = max14577_devs; + mfd_devs_size = ARRAY_SIZE(max14577_devs); + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT; + break; + } + ret = regmap_add_irq_chip(max14577->regmap, max14577->irq, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, 0, - &max14577_irq_chip, + irq_flags, 0, irq_chip, &max14577->irq_data); if (ret != 0) { dev_err(&i2c->dev, "Failed to request IRQ %d: %d\n", @@ -174,8 +362,15 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return ret; } - ret = mfd_add_devices(max14577->dev, -1, max14577_devs, - ARRAY_SIZE(max14577_devs), NULL, 0, + /* Max77836 specific initialization code (additional regmap) */ + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) { + ret = max77836_init(max14577); + if (ret < 0) + goto err_max77836; + } + + ret = mfd_add_devices(max14577->dev, -1, mfd_devs, + mfd_devs_size, NULL, 0, regmap_irq_get_domain(max14577->irq_data)); if (ret < 0) goto err_mfd; @@ -185,6 +380,9 @@ static int max14577_i2c_probe(struct i2c_client *i2c, return 0; err_mfd: + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); +err_max77836: regmap_del_irq_chip(max14577->irq, max14577->irq_data); return ret; @@ -196,12 +394,15 @@ static int max14577_i2c_remove(struct i2c_client *i2c) mfd_remove_devices(max14577->dev); regmap_del_irq_chip(max14577->irq, max14577->irq_data); + if (max14577->dev_type == MAXIM_DEVICE_TYPE_MAX77836) + max77836_remove(max14577); return 0; } static const struct i2c_device_id max14577_i2c_id[] = { { "max14577", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836", MAXIM_DEVICE_TYPE_MAX77836, }, { } }; MODULE_DEVICE_TABLE(i2c, max14577_i2c_id); @@ -274,5 +475,5 @@ static void __exit max14577_i2c_exit(void) module_exit(max14577_i2c_exit); MODULE_AUTHOR("Chanwoo Choi , Krzysztof Kozlowski "); -MODULE_DESCRIPTION("MAXIM 14577 multi-function core driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 multi-function core driver"); MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index 989183d232cd..e301bd19b067 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -1,7 +1,7 @@ /* - * max14577-private.h - Common API for the Maxim 14577 internal sub chip + * max14577-private.h - Common API for the Maxim 14577/77836 internal sub chip * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi * Krzysztof Kozlowski * @@ -22,9 +22,14 @@ #include #include +#define I2C_ADDR_PMIC (0x46 >> 1) +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_FG (0x6C >> 1) + enum maxim_device_type { MAXIM_DEVICE_TYPE_UNKNOWN = 0, MAXIM_DEVICE_TYPE_MAX14577, + MAXIM_DEVICE_TYPE_MAX77836, MAXIM_DEVICE_TYPE_NUM, }; @@ -88,6 +93,7 @@ enum max14577_muic_charger_type { #define MAX14577_INT2_DCDTMR_MASK BIT(2) #define MAX14577_INT2_DBCHG_MASK BIT(3) #define MAX14577_INT2_VBVOLT_MASK BIT(4) +#define MAX77836_INT2_VIDRM_MASK BIT(5) #define MAX14577_INT3_EOC_MASK BIT(0) #define MAX14577_INT3_CGMBC_MASK BIT(1) @@ -104,9 +110,11 @@ enum max14577_muic_charger_type { #define STATUS1_ADC_SHIFT 0 #define STATUS1_ADCLOW_SHIFT 5 #define STATUS1_ADCERR_SHIFT 6 +#define MAX77836_STATUS1_ADC1K_SHIFT 7 #define STATUS1_ADC_MASK (0x1f << STATUS1_ADC_SHIFT) -#define STATUS1_ADCLOW_MASK (0x1 << STATUS1_ADCLOW_SHIFT) -#define STATUS1_ADCERR_MASK (0x1 << STATUS1_ADCERR_SHIFT) +#define STATUS1_ADCLOW_MASK BIT(STATUS1_ADCLOW_SHIFT) +#define STATUS1_ADCERR_MASK BIT(STATUS1_ADCERR_SHIFT) +#define MAX77836_STATUS1_ADC1K_MASK BIT(MAX77836_STATUS1_ADC1K_SHIFT) /* MAX14577 STATUS2 register */ #define STATUS2_CHGTYP_SHIFT 0 @@ -114,11 +122,13 @@ enum max14577_muic_charger_type { #define STATUS2_DCDTMR_SHIFT 4 #define STATUS2_DBCHG_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 (0x1 << STATUS2_CHGDETRUN_SHIFT) -#define STATUS2_DCDTMR_MASK (0x1 << STATUS2_DCDTMR_SHIFT) -#define STATUS2_DBCHG_MASK (0x1 << STATUS2_DBCHG_SHIFT) -#define STATUS2_VBVOLT_MASK (0x1 << STATUS2_VBVOLT_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 STATUS2_VBVOLT_MASK BIT(STATUS2_VBVOLT_SHIFT) +#define MAX77836_STATUS2_VIDRM_MASK BIT(MAX77836_STATUS2_VIDRM_SHIFT) /* MAX14577 CONTROL1 register */ #define COMN1SW_SHIFT 0 @@ -127,8 +137,8 @@ enum max14577_muic_charger_type { #define IDBEN_SHIFT 7 #define COMN1SW_MASK (0x7 << COMN1SW_SHIFT) #define COMP2SW_MASK (0x7 << COMP2SW_SHIFT) -#define MICEN_MASK (0x1 << MICEN_SHIFT) -#define IDBEN_MASK (0x1 << IDBEN_SHIFT) +#define MICEN_MASK BIT(MICEN_SHIFT) +#define IDBEN_MASK BIT(IDBEN_SHIFT) #define CLEAR_IDBEN_MICEN_MASK (COMN1SW_MASK | COMP2SW_MASK) #define CTRL1_SW_USB ((1 << COMP2SW_SHIFT) \ | (1 << COMN1SW_SHIFT)) @@ -148,14 +158,14 @@ enum max14577_muic_charger_type { #define CTRL2_ACCDET_SHIFT (5) #define CTRL2_USBCPINT_SHIFT (6) #define CTRL2_RCPS_SHIFT (7) -#define CTRL2_LOWPWR_MASK (0x1 << CTRL2_LOWPWR_SHIFT) -#define CTRL2_ADCEN_MASK (0x1 << CTRL2_ADCEN_SHIFT) -#define CTRL2_CPEN_MASK (0x1 << CTRL2_CPEN_SHIFT) -#define CTRL2_SFOUTASRT_MASK (0x1 << CTRL2_SFOUTASRT_SHIFT) -#define CTRL2_SFOUTORD_MASK (0x1 << CTRL2_SFOUTORD_SHIFT) -#define CTRL2_ACCDET_MASK (0x1 << CTRL2_ACCDET_SHIFT) -#define CTRL2_USBCPINT_MASK (0x1 << CTRL2_USBCPINT_SHIFT) -#define CTRL2_RCPS_MASK (0x1 << CTR2_RCPS_SHIFT) +#define CTRL2_LOWPWR_MASK BIT(CTRL2_LOWPWR_SHIFT) +#define CTRL2_ADCEN_MASK BIT(CTRL2_ADCEN_SHIFT) +#define CTRL2_CPEN_MASK BIT(CTRL2_CPEN_SHIFT) +#define CTRL2_SFOUTASRT_MASK BIT(CTRL2_SFOUTASRT_SHIFT) +#define CTRL2_SFOUTORD_MASK BIT(CTRL2_SFOUTORD_SHIFT) +#define CTRL2_ACCDET_MASK BIT(CTRL2_ACCDET_SHIFT) +#define CTRL2_USBCPINT_MASK BIT(CTRL2_USBCPINT_SHIFT) +#define CTRL2_RCPS_MASK BIT(CTRL2_RCPS_SHIFT) #define CTRL2_CPEN1_LOWPWR0 ((1 << CTRL2_CPEN_SHIFT) | \ (0 << CTRL2_LOWPWR_SHIFT)) @@ -203,14 +213,14 @@ enum max14577_charger_reg { #define CDETCTRL1_DBEXIT_SHIFT 5 #define CDETCTRL1_DBIDLE_SHIFT 6 #define CDETCTRL1_CDPDET_SHIFT 7 -#define CDETCTRL1_CHGDETEN_MASK (0x1 << CDETCTRL1_CHGDETEN_SHIFT) -#define CDETCTRL1_CHGTYPMAN_MASK (0x1 << CDETCTRL1_CHGTYPMAN_SHIFT) -#define CDETCTRL1_DCDEN_MASK (0x1 << CDETCTRL1_DCDEN_SHIFT) -#define CDETCTRL1_DCD2SCT_MASK (0x1 << CDETCTRL1_DCD2SCT_SHIFT) -#define CDETCTRL1_DCHKTM_MASK (0x1 << CDETCTRL1_DCHKTM_SHIFT) -#define CDETCTRL1_DBEXIT_MASK (0x1 << CDETCTRL1_DBEXIT_SHIFT) -#define CDETCTRL1_DBIDLE_MASK (0x1 << CDETCTRL1_DBIDLE_SHIFT) -#define CDETCTRL1_CDPDET_MASK (0x1 << CDETCTRL1_CDPDET_SHIFT) +#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 CDETCTRL1_DBIDLE_MASK BIT(CDETCTRL1_DBIDLE_SHIFT) +#define CDETCTRL1_CDPDET_MASK BIT(CDETCTRL1_CDPDET_SHIFT) /* MAX14577 CHGCTRL1 register */ #define CHGCTRL1_TCHW_SHIFT 4 @@ -218,9 +228,9 @@ enum max14577_charger_reg { /* MAX14577 CHGCTRL2 register */ #define CHGCTRL2_MBCHOSTEN_SHIFT 6 -#define CHGCTRL2_MBCHOSTEN_MASK (0x1 << CHGCTRL2_MBCHOSTEN_SHIFT) +#define CHGCTRL2_MBCHOSTEN_MASK BIT(CHGCTRL2_MBCHOSTEN_SHIFT) #define CHGCTRL2_VCHGR_RC_SHIFT 7 -#define CHGCTRL2_VCHGR_RC_MASK (0x1 << CHGCTRL2_VCHGR_RC_SHIFT) +#define CHGCTRL2_VCHGR_RC_MASK BIT(CHGCTRL2_VCHGR_RC_SHIFT) /* MAX14577 CHGCTRL3 register */ #define CHGCTRL3_MBCCVWRC_SHIFT 0 @@ -230,7 +240,7 @@ enum max14577_charger_reg { #define CHGCTRL4_MBCICHWRCH_SHIFT 0 #define CHGCTRL4_MBCICHWRCH_MASK (0xf << CHGCTRL4_MBCICHWRCH_SHIFT) #define CHGCTRL4_MBCICHWRCL_SHIFT 4 -#define CHGCTRL4_MBCICHWRCL_MASK (0x1 << CHGCTRL4_MBCICHWRCL_SHIFT) +#define CHGCTRL4_MBCICHWRCL_MASK BIT(CHGCTRL4_MBCICHWRCL_SHIFT) /* MAX14577 CHGCTRL5 register */ #define CHGCTRL5_EOCS_SHIFT 0 @@ -238,7 +248,7 @@ enum max14577_charger_reg { /* MAX14577 CHGCTRL6 register */ #define CHGCTRL6_AUTOSTOP_SHIFT 5 -#define CHGCTRL6_AUTOSTOP_MASK (0x1 << CHGCTRL6_AUTOSTOP_SHIFT) +#define CHGCTRL6_AUTOSTOP_MASK BIT(CHGCTRL6_AUTOSTOP_SHIFT) /* MAX14577 CHGCTRL7 register */ #define CHGCTRL7_OTPCGHCVS_SHIFT 0 @@ -253,6 +263,70 @@ enum max14577_charger_reg { /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000 +/* Slave addr = 0x46: PMIC */ +enum max77836_pmic_reg { + MAX77836_PMIC_REG_PMIC_ID = 0x20, + MAX77836_PMIC_REG_PMIC_REV = 0x21, + MAX77836_PMIC_REG_INTSRC = 0x22, + MAX77836_PMIC_REG_INTSRC_MASK = 0x23, + MAX77836_PMIC_REG_TOPSYS_INT = 0x24, + MAX77836_PMIC_REG_TOPSYS_INT_MASK = 0x26, + MAX77836_PMIC_REG_TOPSYS_STAT = 0x28, + MAX77836_PMIC_REG_MRSTB_CNTL = 0x2A, + MAX77836_PMIC_REG_LSCNFG = 0x2B, + + MAX77836_LDO_REG_CNFG1_LDO1 = 0x51, + MAX77836_LDO_REG_CNFG2_LDO1 = 0x52, + MAX77836_LDO_REG_CNFG1_LDO2 = 0x53, + MAX77836_LDO_REG_CNFG2_LDO2 = 0x54, + MAX77836_LDO_REG_CNFG_LDO_BIAS = 0x55, + + MAX77836_COMP_REG_COMP1 = 0x60, + + MAX77836_PMIC_REG_END, +}; + +#define MAX77836_INTSRC_MASK_TOP_INT_SHIFT 1 +#define MAX77836_INTSRC_MASK_MUIC_CHG_INT_SHIFT 3 +#define MAX77836_INTSRC_MASK_TOP_INT_MASK BIT(MAX77836_INTSRC_MASK_TOP_INT_SHIFT) +#define MAX77836_INTSRC_MASK_MUIC_CHG_INT_MASK BIT(MAX77836_INTSRC_MASK_MUIC_CHG_INT_SHIFT) + +/* MAX77836 PMIC interrupts */ +#define MAX77836_TOPSYS_INT_T120C_SHIFT 0 +#define MAX77836_TOPSYS_INT_T140C_SHIFT 1 +#define MAX77836_TOPSYS_INT_T120C_MASK BIT(MAX77836_TOPSYS_INT_T120C_SHIFT) +#define MAX77836_TOPSYS_INT_T140C_MASK BIT(MAX77836_TOPSYS_INT_T140C_SHIFT) + +/* Slave addr = 0x6C: Fuel-Gauge/Battery */ +enum max77836_fg_reg { + MAX77836_FG_REG_VCELL_MSB = 0x02, + MAX77836_FG_REG_VCELL_LSB = 0x03, + MAX77836_FG_REG_SOC_MSB = 0x04, + MAX77836_FG_REG_SOC_LSB = 0x05, + MAX77836_FG_REG_MODE_H = 0x06, + MAX77836_FG_REG_MODE_L = 0x07, + MAX77836_FG_REG_VERSION_MSB = 0x08, + MAX77836_FG_REG_VERSION_LSB = 0x09, + MAX77836_FG_REG_HIBRT_H = 0x0A, + MAX77836_FG_REG_HIBRT_L = 0x0B, + MAX77836_FG_REG_CONFIG_H = 0x0C, + MAX77836_FG_REG_CONFIG_L = 0x0D, + MAX77836_FG_REG_VALRT_MIN = 0x14, + MAX77836_FG_REG_VALRT_MAX = 0x15, + MAX77836_FG_REG_CRATE_MSB = 0x16, + MAX77836_FG_REG_CRATE_LSB = 0x17, + MAX77836_FG_REG_VRESET = 0x18, + MAX77836_FG_REG_FGID = 0x19, + MAX77836_FG_REG_STATUS_H = 0x1A, + MAX77836_FG_REG_STATUS_L = 0x1B, + /* + * TODO: TABLE registers + * TODO: CMD register + */ + + MAX77836_FG_REG_END, +}; + enum max14577_irq { /* INT1 */ MAX14577_IRQ_INT1_ADC, @@ -272,17 +346,24 @@ enum max14577_irq { MAX14577_IRQ_INT3_OVP, MAX14577_IRQ_INT3_MBCCHGERR, + /* TOPSYS_INT, only MAX77836 */ + MAX77836_IRQ_TOPSYS_T140C, + MAX77836_IRQ_TOPSYS_T120C, + MAX14577_IRQ_NUM, }; struct max14577 { struct device *dev; struct i2c_client *i2c; /* Slave addr = 0x4A */ + struct i2c_client *i2c_pmic; /* Slave addr = 0x46 */ enum maxim_device_type dev_type; - struct regmap *regmap; + struct regmap *regmap; /* For MUIC and Charger */ + struct regmap *regmap_pmic; - struct regmap_irq_chip_data *irq_data; + struct regmap_irq_chip_data *irq_data; /* For MUIC and Charger */ + struct regmap_irq_chip_data *irq_data_pmic; int irq; }; diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h index 736d39c3ec0d..08b449159fd1 100644 --- a/include/linux/mfd/max14577.h +++ b/include/linux/mfd/max14577.h @@ -1,7 +1,7 @@ /* - * max14577.h - Driver for the Maxim 14577 + * max14577.h - Driver for the Maxim 14577/77836 * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2014 Samsung Electrnoics * Chanwoo Choi * Krzysztof Kozlowski * @@ -20,6 +20,9 @@ * MAX14577 has MUIC, Charger devices. * The devices share the same I2C bus and interrupt line * included in this mfd driver. + * + * MAX77836 has additional PMIC and Fuel-Gauge on different I2C slave + * addresses. */ #ifndef __MAX14577_H__ -- cgit v1.2.3 From 4706a5253bcc502a5889feb98392ea7b15dd936e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:19 +0200 Subject: extcon: max14577: Add support for MAX77836 Add support for MAX77836 chipset to the max14577 extcon driver. The MAX77836 MUIC has additional interrupts (VIDRM, ADC1K) so IRQ handling is split up into two functions: max14577_parse_irq() and max77836_parse_irq(). Signed-off-by: Krzysztof Kozlowski Acked-by: Chanwoo Choi Tested-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/extcon/Kconfig | 4 +- drivers/extcon/extcon-max14577.c | 109 +++++++++++++++++++++++++++++------ drivers/mfd/max14577.c | 1 + include/linux/mfd/max14577-private.h | 3 + 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index be56e8ac95e6..aebde489c291 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -28,13 +28,13 @@ config EXTCON_ADC_JACK Say Y here to enable extcon device driver based on ADC values. config EXTCON_MAX14577 - tristate "MAX14577 EXTCON Support" + tristate "MAX14577/77836 EXTCON Support" depends on MFD_MAX14577 select IRQ_DOMAIN select REGMAP_I2C help If you say yes here you get support for the MUIC device of - Maxim MAX14577 PMIC. The MAX14577 MUIC is a USB port accessory + Maxim MAX14577/77836. The MAX14577/77836 MUIC is a USB port accessory detector and switch. config EXTCON_MAX77693 diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 1513013a92f1..c76734a70171 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -1,8 +1,9 @@ /* - * extcon-max14577.c - MAX14577 extcon driver to support MAX14577 MUIC + * extcon-max14577.c - MAX14577/77836 extcon driver to support MUIC * - * Copyright (C) 2013 Samsung Electrnoics + * Copyright (C) 2013,2014 Samsung Electrnoics * Chanwoo Choi + * Krzysztof Kozlowski * * 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 @@ -62,6 +63,19 @@ static struct max14577_muic_irq max14577_muic_irqs[] = { { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, }; +static struct max14577_muic_irq max77836_muic_irqs[] = { + { MAX14577_IRQ_INT1_ADC, "muic-ADC" }, + { MAX14577_IRQ_INT1_ADCLOW, "muic-ADCLOW" }, + { MAX14577_IRQ_INT1_ADCERR, "muic-ADCError" }, + { MAX77836_IRQ_INT1_ADC1K, "muic-ADC1K" }, + { MAX14577_IRQ_INT2_CHGTYP, "muic-CHGTYP" }, + { MAX14577_IRQ_INT2_CHGDETRUN, "muic-CHGDETRUN" }, + { MAX14577_IRQ_INT2_DCDTMR, "muic-DCDTMR" }, + { MAX14577_IRQ_INT2_DBCHG, "muic-DBCHG" }, + { MAX14577_IRQ_INT2_VBVOLT, "muic-VBVOLT" }, + { MAX77836_IRQ_INT2_VIDRM, "muic-VIDRM" }, +}; + struct max14577_muic_info { struct device *dev; struct max14577 *max14577; @@ -529,21 +543,12 @@ static void max14577_muic_irq_work(struct work_struct *work) return; } -static irqreturn_t max14577_muic_irq_handler(int irq, void *data) +/* + * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. + * Returns 0 if irq_type does not match registered IRQ for this device type. + */ +static int max14577_parse_irq(struct max14577_muic_info *info, int irq_type) { - struct max14577_muic_info *info = data; - int i, irq_type = -1; - - /* - * We may be called multiple times for different nested IRQ-s. - * Including changes in INT1_ADC and INT2_CGHTYP at once. - * However we only need to know whether it was ADC, charger - * or both interrupts so decode IRQ and turn on proper flags. - */ - for (i = 0; i < info->muic_irqs_num; i++) - if (irq == info->muic_irqs[i].virq) - irq_type = info->muic_irqs[i].irq; - switch (irq_type) { case MAX14577_IRQ_INT1_ADC: case MAX14577_IRQ_INT1_ADCLOW: @@ -551,7 +556,7 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) /* Handle all of accessory except for type of charger accessory */ info->irq_adc = true; - break; + return 1; case MAX14577_IRQ_INT2_CHGTYP: case MAX14577_IRQ_INT2_CHGDETRUN: case MAX14577_IRQ_INT2_DCDTMR: @@ -559,8 +564,62 @@ static irqreturn_t max14577_muic_irq_handler(int irq, void *data) case MAX14577_IRQ_INT2_VBVOLT: /* Handle charger accessory */ info->irq_chg = true; + return 1; + default: + return 0; + } +} + +/* + * Sets irq_adc or irq_chg in max14577_muic_info and returns 1. + * Returns 0 if irq_type does not match registered IRQ for this device type. + */ +static int max77836_parse_irq(struct max14577_muic_info *info, int irq_type) +{ + /* First check common max14577 interrupts */ + if (max14577_parse_irq(info, irq_type)) + return 1; + + switch (irq_type) { + case MAX77836_IRQ_INT1_ADC1K: + info->irq_adc = true; + return 1; + case MAX77836_IRQ_INT2_VIDRM: + /* Handle charger accessory */ + info->irq_chg = true; + return 1; + default: + return 0; + } +} + +static irqreturn_t max14577_muic_irq_handler(int irq, void *data) +{ + struct max14577_muic_info *info = data; + int i, irq_type = -1; + bool irq_parsed; + + /* + * We may be called multiple times for different nested IRQ-s. + * Including changes in INT1_ADC and INT2_CGHTYP at once. + * However we only need to know whether it was ADC, charger + * or both interrupts so decode IRQ and turn on proper flags. + */ + for (i = 0; i < info->muic_irqs_num; i++) + if (irq == info->muic_irqs[i].virq) + irq_type = info->muic_irqs[i].irq; + + switch (info->max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + irq_parsed = max77836_parse_irq(info, irq_type); break; + case MAXIM_DEVICE_TYPE_MAX14577: default: + irq_parsed = max14577_parse_irq(info, irq_type); + break; + } + + if (!irq_parsed) { dev_err(info->dev, "muic interrupt: irq %d occurred, skipped\n", irq_type); return IRQ_HANDLED; @@ -646,6 +705,10 @@ static int max14577_muic_probe(struct platform_device *pdev) INIT_WORK(&info->irq_work, max14577_muic_irq_work); switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + info->muic_irqs = max77836_muic_irqs; + info->muic_irqs_num = ARRAY_SIZE(max77836_muic_irqs); + break; case MAXIM_DEVICE_TYPE_MAX14577: default: info->muic_irqs = max14577_muic_irqs; @@ -744,6 +807,13 @@ static int max14577_muic_remove(struct platform_device *pdev) return 0; } +static const struct platform_device_id max14577_muic_id[] = { + { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_muic_id); + static struct platform_driver max14577_muic_driver = { .driver = { .name = "max14577-muic", @@ -751,11 +821,12 @@ static struct platform_driver max14577_muic_driver = { }, .probe = max14577_muic_probe, .remove = max14577_muic_remove, + .id_table = max14577_muic_id, }; module_platform_driver(max14577_muic_driver); -MODULE_DESCRIPTION("MAXIM 14577 Extcon driver"); -MODULE_AUTHOR("Chanwoo Choi "); +MODULE_DESCRIPTION("Maxim 14577/77836 Extcon driver"); +MODULE_AUTHOR("Chanwoo Choi , Krzysztof Kozlowski "); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:extcon-max14577"); diff --git a/drivers/mfd/max14577.c b/drivers/mfd/max14577.c index 20e3b2d81bf0..484d372a4892 100644 --- a/drivers/mfd/max14577.c +++ b/drivers/mfd/max14577.c @@ -147,6 +147,7 @@ static const struct regmap_irq max77836_muic_irqs[] = { { .reg_offset = 0, .mask = MAX14577_INT1_ADC_MASK, }, { .reg_offset = 0, .mask = MAX14577_INT1_ADCLOW_MASK, }, { .reg_offset = 0, .mask = MAX14577_INT1_ADCERR_MASK, }, + { .reg_offset = 0, .mask = MAX77836_INT1_ADC1K_MASK, }, /* INT2 interrupts */ { .reg_offset = 1, .mask = MAX14577_INT2_CHGTYP_MASK, }, { .reg_offset = 1, .mask = MAX14577_INT2_CHGDETRUN_MASK, }, diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index e301bd19b067..a557ae27d8a8 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -87,6 +87,7 @@ enum max14577_muic_charger_type { #define MAX14577_INT1_ADC_MASK BIT(0) #define MAX14577_INT1_ADCLOW_MASK BIT(1) #define MAX14577_INT1_ADCERR_MASK BIT(2) +#define MAX77836_INT1_ADC1K_MASK BIT(3) #define MAX14577_INT2_CHGTYP_MASK BIT(0) #define MAX14577_INT2_CHGDETRUN_MASK BIT(1) @@ -332,6 +333,7 @@ enum max14577_irq { MAX14577_IRQ_INT1_ADC, MAX14577_IRQ_INT1_ADCLOW, MAX14577_IRQ_INT1_ADCERR, + MAX77836_IRQ_INT1_ADC1K, /* INT2 */ MAX14577_IRQ_INT2_CHGTYP, @@ -339,6 +341,7 @@ enum max14577_irq { MAX14577_IRQ_INT2_DCDTMR, MAX14577_IRQ_INT2_DBCHG, MAX14577_IRQ_INT2_VBVOLT, + MAX77836_IRQ_INT2_VIDRM, /* INT3 */ MAX14577_IRQ_INT3_EOC, -- cgit v1.2.3 From 8a82b408acad29161c43072727151d373e68116a Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 14 Apr 2014 11:17:20 +0200 Subject: regulator: max14577: Add support for MAX77836 regulators Add support for MAX77836 chipset and its additional two LDO regulators. These LDO regulators are controlled by the PMIC block with additional regmap (different I2C slave address). The MAX77836 charger and safeout regulators are almost identical to MAX14577. The registers layout is the same, except values for charger's current. The patch adds simple mapping between device type and supported current by the charger regulator. Signed-off-by: Krzysztof Kozlowski Reviewed-by: Mark Brown Signed-off-by: Lee Jones --- drivers/regulator/Kconfig | 7 +- drivers/regulator/max14577.c | 277 ++++++++++++++++++++++++++++++----- include/linux/mfd/max14577-private.h | 32 ++++ include/linux/mfd/max14577.h | 12 +- 4 files changed, 289 insertions(+), 39 deletions(-) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 903eb37f047a..f0cc9e6dac3a 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -266,11 +266,12 @@ config REGULATOR_LP8788 This driver supports LP8788 voltage regulator chip. config REGULATOR_MAX14577 - tristate "Maxim 14577 regulator" + tristate "Maxim 14577/77836 regulator" depends on MFD_MAX14577 help - This driver controls a Maxim 14577 regulator via I2C bus. - The regulators include safeout LDO and current regulator 'CHARGER'. + This driver controls a Maxim MAX14577/77836 regulator via I2C bus. + The MAX14577 regulators include safeout LDO and charger current + regulator. The MAX77836 has two additional LDOs. config REGULATOR_MAX1586 tristate "Maxim 1586/1587 voltage regulator" diff --git a/drivers/regulator/max14577.c b/drivers/regulator/max14577.c index ed60baaeceec..5d9c605cf534 100644 --- a/drivers/regulator/max14577.c +++ b/drivers/regulator/max14577.c @@ -1,5 +1,5 @@ /* - * max14577.c - Regulator driver for the Maxim 14577 + * max14577.c - Regulator driver for the Maxim 14577/77836 * * Copyright (C) 2013,2014 Samsung Electronics * Krzysztof Kozlowski @@ -22,6 +22,42 @@ #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); @@ -47,6 +83,9 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) { u8 reg_data; struct regmap *rmap = rdev->regmap; + struct max14577 *max14577 = rdev_get_drvdata(rdev); + const struct maxim_charger_current *limits = + &maxim_charger_currents[max14577->dev_type]; if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; @@ -54,12 +93,11 @@ static int max14577_reg_get_current_limit(struct regulator_dev *rdev) max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL4, ®_data); if ((reg_data & CHGCTRL4_MBCICHWRCL_MASK) == 0) - return MAX14577_REGULATOR_CURRENT_LIMIT_MIN; + return limits->min; reg_data = ((reg_data & CHGCTRL4_MBCICHWRCH_MASK) >> CHGCTRL4_MBCICHWRCH_SHIFT); - return MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START + - reg_data * MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP; + return limits->high_start + reg_data * limits->high_step; } static int max14577_reg_set_current_limit(struct regulator_dev *rdev, @@ -67,33 +105,39 @@ static int max14577_reg_set_current_limit(struct regulator_dev *rdev, { int i, current_bits = 0xf; u8 reg_data; + struct max14577 *max14577 = rdev_get_drvdata(rdev); + const struct maxim_charger_current *limits = + &maxim_charger_currents[max14577->dev_type]; if (rdev_get_id(rdev) != MAX14577_CHARGER) return -EINVAL; - if (min_uA > MAX14577_REGULATOR_CURRENT_LIMIT_MAX || - max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_MIN) + if (min_uA > limits->max || max_uA < limits->min) return -EINVAL; - if (max_uA < MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START) { - /* Less than 200 mA, so set 90mA (turn only Low Bit off) */ + 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 LIMIT_MAX. */ - for (i = MAX14577_REGULATOR_CURRENT_LIMIT_MAX; - i >= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_START; - i -= MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP) { + /* + * 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 200mA-950 mA) */ + + /* 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; @@ -118,7 +162,7 @@ static struct regulator_ops max14577_charger_ops = { .set_current_limit = max14577_reg_set_current_limit, }; -static const struct regulator_desc supported_regulators[] = { +static const struct regulator_desc max14577_supported_regulators[] = { [MAX14577_SAFEOUT] = { .name = "SAFEOUT", .id = MAX14577_SAFEOUT, @@ -141,16 +185,88 @@ static const struct regulator_desc supported_regulators[] = { }, }; +static struct regulator_ops max77836_ldo_ops = { + .is_enabled = regulator_is_enabled_regmap, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + /* TODO: add .set_suspend_mode */ +}; + +static const struct regulator_desc max77836_supported_regulators[] = { + [MAX14577_SAFEOUT] = { + .name = "SAFEOUT", + .id = MAX14577_SAFEOUT, + .ops = &max14577_safeout_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = 1, + .min_uV = MAX14577_REGULATOR_SAFEOUT_VOLTAGE, + .enable_reg = MAX14577_REG_CONTROL2, + .enable_mask = CTRL2_SFOUTORD_MASK, + }, + [MAX14577_CHARGER] = { + .name = "CHARGER", + .id = MAX14577_CHARGER, + .ops = &max14577_charger_ops, + .type = REGULATOR_CURRENT, + .owner = THIS_MODULE, + .enable_reg = MAX14577_CHG_REG_CHG_CTRL2, + .enable_mask = CHGCTRL2_MBCHOSTEN_MASK, + }, + [MAX77836_LDO1] = { + .name = "LDO1", + .id = MAX77836_LDO1, + .ops = &max77836_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, + .min_uV = MAX77836_REGULATOR_LDO_VOLTAGE_MIN, + .uV_step = MAX77836_REGULATOR_LDO_VOLTAGE_STEP, + .enable_reg = MAX77836_LDO_REG_CNFG1_LDO1, + .enable_mask = MAX77836_CNFG1_LDO_PWRMD_MASK, + .vsel_reg = MAX77836_LDO_REG_CNFG1_LDO1, + .vsel_mask = MAX77836_CNFG1_LDO_TV_MASK, + }, + [MAX77836_LDO2] = { + .name = "LDO2", + .id = MAX77836_LDO2, + .ops = &max77836_ldo_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, + .n_voltages = MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM, + .min_uV = MAX77836_REGULATOR_LDO_VOLTAGE_MIN, + .uV_step = MAX77836_REGULATOR_LDO_VOLTAGE_STEP, + .enable_reg = MAX77836_LDO_REG_CNFG1_LDO2, + .enable_mask = MAX77836_CNFG1_LDO_PWRMD_MASK, + .vsel_reg = MAX77836_LDO_REG_CNFG1_LDO2, + .vsel_mask = MAX77836_CNFG1_LDO_TV_MASK, + }, +}; + #ifdef CONFIG_OF static struct of_regulator_match max14577_regulator_matches[] = { { .name = "SAFEOUT", }, { .name = "CHARGER", }, }; -static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) +static struct of_regulator_match max77836_regulator_matches[] = { + { .name = "SAFEOUT", }, + { .name = "CHARGER", }, + { .name = "LDO1", }, + { .name = "LDO2", }, +}; + +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, + enum maxim_device_type dev_type) { int ret; struct device_node *np; + struct of_regulator_match *regulator_matches; + unsigned int regulator_matches_size; np = of_get_child_by_name(pdev->dev.parent->of_node, "regulators"); if (!np) { @@ -158,8 +274,19 @@ static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) return -EINVAL; } - ret = of_regulator_match(&pdev->dev, np, max14577_regulator_matches, - MAX14577_REG_MAX); + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + regulator_matches = max77836_regulator_matches; + regulator_matches_size = ARRAY_SIZE(max77836_regulator_matches); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + regulator_matches = max14577_regulator_matches; + regulator_matches_size = ARRAY_SIZE(max14577_regulator_matches); + } + + ret = of_regulator_match(&pdev->dev, np, regulator_matches, + regulator_matches_size); if (ret < 0) dev_err(&pdev->dev, "Error parsing regulator init data: %d\n", ret); else @@ -170,31 +297,74 @@ static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) return ret; } -static inline struct regulator_init_data *match_init_data(int index) +static inline struct regulator_init_data *match_init_data(int index, + enum maxim_device_type dev_type) { - return max14577_regulator_matches[index].init_data; + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + return max77836_regulator_matches[index].init_data; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577_regulator_matches[index].init_data; + } } -static inline struct device_node *match_of_node(int index) +static inline struct device_node *match_of_node(int index, + enum maxim_device_type dev_type) { - return max14577_regulator_matches[index].of_node; + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + return max77836_regulator_matches[index].of_node; + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577_regulator_matches[index].of_node; + } } #else /* CONFIG_OF */ -static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev) +static int max14577_regulator_dt_parse_pdata(struct platform_device *pdev, + enum maxim_device_type dev_type) { return 0; } -static inline struct regulator_init_data *match_init_data(int index) +static inline struct regulator_init_data *match_init_data(int index, + enum maxim_device_type dev_type) { return NULL; } -static inline struct device_node *match_of_node(int index) +static inline struct device_node *match_of_node(int index, + enum maxim_device_type dev_type) { return NULL; } #endif /* CONFIG_OF */ +/** + * Registers for regulators of max77836 use different I2C slave addresses so + * different regmaps must be used for them. + * + * Returns proper regmap for accessing regulator passed by id. + */ +static struct regmap *max14577_get_regmap(struct max14577 *max14577, + int reg_id) +{ + switch (max14577->dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + switch (reg_id) { + case MAX77836_SAFEOUT ... MAX77836_CHARGER: + return max14577->regmap; + default: + /* MAX77836_LDO1 ... MAX77836_LDO2 */ + return max14577->regmap_pmic; + } + + case MAXIM_DEVICE_TYPE_MAX14577: + default: + return max14577->regmap; + } +} static int max14577_regulator_probe(struct platform_device *pdev) { @@ -202,15 +372,29 @@ static int max14577_regulator_probe(struct platform_device *pdev) struct max14577_platform_data *pdata = dev_get_platdata(max14577->dev); int i, ret; struct regulator_config config = {}; + const struct regulator_desc *supported_regulators; + unsigned int supported_regulators_size; + enum maxim_device_type dev_type = max14577->dev_type; - ret = max14577_regulator_dt_parse_pdata(pdev); + ret = max14577_regulator_dt_parse_pdata(pdev, dev_type); if (ret) return ret; + switch (dev_type) { + case MAXIM_DEVICE_TYPE_MAX77836: + supported_regulators = max77836_supported_regulators; + supported_regulators_size = ARRAY_SIZE(max77836_supported_regulators); + break; + case MAXIM_DEVICE_TYPE_MAX14577: + default: + supported_regulators = max14577_supported_regulators; + supported_regulators_size = ARRAY_SIZE(max14577_supported_regulators); + } + config.dev = &pdev->dev; - config.regmap = max14577->regmap; + config.driver_data = max14577; - for (i = 0; i < ARRAY_SIZE(supported_regulators); i++) { + for (i = 0; i < supported_regulators_size; i++) { struct regulator_dev *regulator; /* * Index of supported_regulators[] is also the id and must @@ -220,17 +404,19 @@ static int max14577_regulator_probe(struct platform_device *pdev) config.init_data = pdata->regulators[i].initdata; config.of_node = pdata->regulators[i].of_node; } else { - config.init_data = match_init_data(i); - config.of_node = match_of_node(i); + config.init_data = match_init_data(i, dev_type); + config.of_node = match_of_node(i, dev_type); } + config.regmap = max14577_get_regmap(max14577, + supported_regulators[i].id); regulator = devm_regulator_register(&pdev->dev, &supported_regulators[i], &config); if (IS_ERR(regulator)) { ret = PTR_ERR(regulator); dev_err(&pdev->dev, - "Regulator init failed for ID %d with error: %d\n", - i, ret); + "Regulator init failed for %d/%s with error: %d\n", + i, supported_regulators[i].name, ret); return ret; } } @@ -238,20 +424,41 @@ static int max14577_regulator_probe(struct platform_device *pdev) return ret; } +static const struct platform_device_id max14577_regulator_id[] = { + { "max14577-regulator", MAXIM_DEVICE_TYPE_MAX14577, }, + { "max77836-regulator", MAXIM_DEVICE_TYPE_MAX77836, }, + { } +}; +MODULE_DEVICE_TABLE(platform, max14577_regulator_id); + static struct platform_driver max14577_regulator_driver = { .driver = { .owner = THIS_MODULE, .name = "max14577-regulator", }, - .probe = max14577_regulator_probe, + .probe = max14577_regulator_probe, + .id_table = max14577_regulator_id, }; 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(ARRAY_SIZE(supported_regulators) != MAX14577_REG_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); + + BUILD_BUG_ON(MAX77836_REGULATOR_LDO_VOLTAGE_MIN + + (MAX77836_REGULATOR_LDO_VOLTAGE_STEP * + (MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM - 1)) != + MAX77836_REGULATOR_LDO_VOLTAGE_MAX); return platform_driver_register(&max14577_regulator_driver); } @@ -264,6 +471,6 @@ static void __exit max14577_regulator_exit(void) module_exit(max14577_regulator_exit); MODULE_AUTHOR("Krzysztof Kozlowski "); -MODULE_DESCRIPTION("MAXIM 14577 regulator driver"); +MODULE_DESCRIPTION("Maxim 14577/77836 regulator driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:max14577-regulator"); diff --git a/include/linux/mfd/max14577-private.h b/include/linux/mfd/max14577-private.h index a557ae27d8a8..499253604026 100644 --- a/include/linux/mfd/max14577-private.h +++ b/include/linux/mfd/max14577-private.h @@ -261,9 +261,21 @@ enum max14577_charger_reg { #define MAX14577_REGULATOR_CURRENT_LIMIT_HIGH_STEP 50000 #define MAX14577_REGULATOR_CURRENT_LIMIT_MAX 950000 +/* 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 + /* MAX14577 regulator SFOUT LDO voltage, fixed, uV */ #define MAX14577_REGULATOR_SAFEOUT_VOLTAGE 4900000 +/* MAX77836 regulator LDOx voltage, uV */ +#define MAX77836_REGULATOR_LDO_VOLTAGE_MIN 800000 +#define MAX77836_REGULATOR_LDO_VOLTAGE_MAX 3950000 +#define MAX77836_REGULATOR_LDO_VOLTAGE_STEP 50000 +#define MAX77836_REGULATOR_LDO_VOLTAGE_STEPS_NUM 64 + /* Slave addr = 0x46: PMIC */ enum max77836_pmic_reg { MAX77836_PMIC_REG_PMIC_ID = 0x20, @@ -298,6 +310,26 @@ enum max77836_pmic_reg { #define MAX77836_TOPSYS_INT_T120C_MASK BIT(MAX77836_TOPSYS_INT_T120C_SHIFT) #define MAX77836_TOPSYS_INT_T140C_MASK BIT(MAX77836_TOPSYS_INT_T140C_SHIFT) +/* LDO1/LDO2 CONFIG1 register */ +#define MAX77836_CNFG1_LDO_PWRMD_SHIFT 6 +#define MAX77836_CNFG1_LDO_TV_SHIFT 0 +#define MAX77836_CNFG1_LDO_PWRMD_MASK (0x3 << MAX77836_CNFG1_LDO_PWRMD_SHIFT) +#define MAX77836_CNFG1_LDO_TV_MASK (0x3f << MAX77836_CNFG1_LDO_TV_SHIFT) + +/* LDO1/LDO2 CONFIG2 register */ +#define MAX77836_CNFG2_LDO_OVCLMPEN_SHIFT 7 +#define MAX77836_CNFG2_LDO_ALPMEN_SHIFT 6 +#define MAX77836_CNFG2_LDO_COMP_SHIFT 4 +#define MAX77836_CNFG2_LDO_POK_SHIFT 3 +#define MAX77836_CNFG2_LDO_ADE_SHIFT 1 +#define MAX77836_CNFG2_LDO_SS_SHIFT 0 +#define MAX77836_CNFG2_LDO_OVCLMPEN_MASK BIT(MAX77836_CNFG2_LDO_OVCLMPEN_SHIFT) +#define MAX77836_CNFG2_LDO_ALPMEN_MASK BIT(MAX77836_CNFG2_LDO_ALPMEN_SHIFT) +#define MAX77836_CNFG2_LDO_COMP_MASK (0x3 << MAX77836_CNFG2_LDO_COMP_SHIFT) +#define MAX77836_CNFG2_LDO_POK_MASK BIT(MAX77836_CNFG2_LDO_POK_SHIFT) +#define MAX77836_CNFG2_LDO_ADE_MASK BIT(MAX77836_CNFG2_LDO_ADE_SHIFT) +#define MAX77836_CNFG2_LDO_SS_MASK BIT(MAX77836_CNFG2_LDO_SS_SHIFT) + /* Slave addr = 0x6C: Fuel-Gauge/Battery */ enum max77836_fg_reg { MAX77836_FG_REG_VCELL_MSB = 0x02, diff --git a/include/linux/mfd/max14577.h b/include/linux/mfd/max14577.h index 08b449159fd1..c83fbed1c7b6 100644 --- a/include/linux/mfd/max14577.h +++ b/include/linux/mfd/max14577.h @@ -35,7 +35,17 @@ enum max14577_regulators { MAX14577_SAFEOUT = 0, MAX14577_CHARGER, - MAX14577_REG_MAX, + MAX14577_REGULATOR_NUM, +}; + +/* MAX77836 regulator IDs */ +enum max77836_regulators { + MAX77836_SAFEOUT = 0, + MAX77836_CHARGER, + MAX77836_LDO1, + MAX77836_LDO2, + + MAX77836_REGULATOR_NUM, }; struct max14577_regulator_platform_data { -- cgit v1.2.3 From d5653f2b7304f05eeb45d84f123cf02f840b8537 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 9 Apr 2014 15:20:12 +0200 Subject: extcon: max77693: Fix two NULL pointer exceptions on missing pdata Fix NULL pointer exceptions when platform data is not supplied. Trace of one exception: Unable to handle kernel NULL pointer dereference at virtual address 00000008 pgd = c0004000 [00000008] *pgd=00000000 Internal error: Oops: 5 [#1] PREEMPT SMP ARM Modules linked in: CPU: 2 PID: 1 Comm: swapper/0 Not tainted 3.14.0-12045-gead5dd4687a6-dirty #1628 task: eea80000 ti: eea88000 task.ti: eea88000 PC is at max77693_muic_probe+0x27c/0x528 LR is at regmap_write+0x50/0x60 pc : [] lr : [] psr: 20000113 sp : eea89e38 ip : 00000000 fp : c098a834 r10: ee1a5a10 r9 : 00000005 r8 : c098a83c r7 : 0000000a r6 : c098a774 r5 : 00000005 r4 : eeb006d0 r3 : c0697bd8 r2 : 00000000 r1 : 00000001 r0 : 00000000 Flags: nzCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c5387d Table: 4000404a DAC: 00000015 Process swapper/0 (pid: 1, stack limit = 0xeea88240) Stack: (0xeea89e38 to 0xeea8a000) 9e20: c08499fc eeb006d0 9e40: 00000000 00000000 c0915f98 00000001 00000000 ee1a5a10 c098a730 c09a88b8 9e60: 00000000 c098a730 c0915f98 00000000 00000000 c02d6aa0 c02d6a88 ee1a5a10 9e80: c0a712c8 c02d54e4 00001204 c0628b00 ee1a5a10 c098a730 ee1a5a44 00000000 9ea0: eea88000 c02d57b4 00000000 c098a730 c02d5728 c02d3a24 ee813e5c eeb9d534 9ec0: c098a730 ee22f700 c097c720 c02d4b14 c08174ec c098a730 00000006 c098a730 9ee0: 00000006 c092fd30 c09b8500 c02d5df8 00000000 c093cbb8 00000006 c0008928 9f00: 000000c3 ef7fc785 00000000 ef7fc794 00000000 c08af968 00000072 eea89f30 9f20: ef7fc85e c065f198 000000c3 c003e87c 00000003 00000000 c092fd3c 00000000 9f40: c08af618 c0826d58 00000006 00000006 c0956f58 c093cbb8 00000006 c092fd30 9f60: c09b8500 000000c3 c092fd3c c08e8510 00000000 c08e8bb0 00000006 00000006 9f80: c08e8510 c0c0c0c0 00000000 c0628fac 00000000 00000000 00000000 00000000 9fa0: 00000000 c0628fb4 00000000 c000f038 00000000 00000000 00000000 00000000 9fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 9fe0: 00000000 00000000 00000000 00000000 00000013 00000000 c0c0c0c0 c0c0c0c0 [] (max77693_muic_probe) from [] (platform_drv_probe+0x18/0x48) [] (platform_drv_probe) from [] (driver_probe_device+0x140/0x384) [] (driver_probe_device) from [] (__driver_attach+0x8c/0x90) [] (__driver_attach) from [] (bus_for_each_dev+0x54/0x88) [] (bus_for_each_dev) from [] (bus_add_driver+0xe8/0x204) [] (bus_add_driver) from [] (driver_register+0x78/0xf4) [] (driver_register) from [] (do_one_initcall+0xc4/0x174) [] (do_one_initcall) from [] (kernel_init_freeable+0xfc/0x1c8) [] (kernel_init_freeable) from [] (kernel_init+0x8/0xec) [] (kernel_init) from [] (ret_from_fork+0x14/0x3c) Code: caffffe7 e59d200c e3550001 b3a05001 (e5923008) ---[ end trace 85db969ce011bde7 ]--- Signed-off-by: Krzysztof Kozlowski Cc: Fixes: 190d7cfc8632 Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77693.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index da268fbc901b..4657a91acf56 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1193,7 +1193,7 @@ static int max77693_muic_probe(struct platform_device *pdev) /* Initialize MUIC register by using platform data or default data */ - if (pdata->muic_data) { + if (pdata && pdata->muic_data) { init_data = pdata->muic_data->init_data; num_init_data = pdata->muic_data->num_init_data; } else { @@ -1226,7 +1226,7 @@ static int max77693_muic_probe(struct platform_device *pdev) = init_data[i].data; } - if (pdata->muic_data) { + if (pdata && pdata->muic_data) { struct max77693_muic_platform_data *muic_pdata = pdata->muic_data; -- cgit v1.2.3 From dfee4111febf3d9ef3a640b2cd6205c75f4e7e3d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 9 Apr 2014 15:20:14 +0200 Subject: extcon: max8997: Fix NULL pointer exception on missing pdata Fix NULL pointer exception when platform data is not supplied. The driver dereferenced pdata pointer where it could be NULL. Signed-off-by: Krzysztof Kozlowski Cc: Fixes: 810d601f07c Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max8997.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 6a00464658c5..5e1b88cecb76 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -715,7 +715,7 @@ static int max8997_muic_probe(struct platform_device *pdev) goto err_irq; } - if (pdata->muic_pdata) { + if (pdata && pdata->muic_pdata) { struct max8997_muic_platform_data *muic_pdata = pdata->muic_pdata; -- cgit v1.2.3 From b8629411bf2c0979ae130511f27fd708e2fd102e Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 9 Apr 2014 15:20:13 +0200 Subject: extcon: max77693: Use power efficient workqueue for delayed cable detection Schedule delayed cable detection work on power efficient workqueue so the scheduler won't wake up idle core for that work. This extends the idle time for CPU cores and conserves power. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77693.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 4657a91acf56..39cd095d103c 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1283,7 +1283,8 @@ static int max77693_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max77693_muic_detect_cable_wq); - schedule_delayed_work(&info->wq_detcable, delay_jiffies); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); return ret; -- cgit v1.2.3 From bf9509e032076dc5850598814c243c45a8b81690 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 9 Apr 2014 15:20:15 +0200 Subject: extcon: max8997: Use power efficient workqueue for delayed cable detection Schedule delayed cable detection work on power efficient workqueue so the scheduler won't wake up idle core for that work. This extends the idle time for CPU cores and conserves power. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max8997.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 5e1b88cecb76..223e6b068c98 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -770,7 +770,8 @@ static int max8997_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max8997_muic_detect_cable_wq); - schedule_delayed_work(&info->wq_detcable, delay_jiffies); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + delay_jiffies); return 0; -- cgit v1.2.3 From 12adef5b49e98eb181b4163c36e2998169e1379b Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 9 Apr 2014 11:56:09 +0200 Subject: extcon: max14577: Fix probe failure on successful work queue In probe the driver queued delayed work for cable detection and returned the result of queue_delayed_work() call. However the return value of queue_delayed_work() does not indicate an error and in normal condition it returns true which means successful work queue. This effectively resulted in probe failure: [ 2.088204] max14577-muic: probe of max77836-muic failed with error 1 Signed-off-by: Krzysztof Kozlowski Cc: Fixes: 962e56bfcf0b ("extcon: max14577: Add extcon-max14577 driver...") Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max14577.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index c76734a70171..4179ce1a23de 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -782,13 +782,8 @@ static int max14577_muic_probe(struct platform_device *pdev) * driver should notify cable state to upper layer. */ INIT_DELAYED_WORK(&info->wq_detcable, max14577_muic_detect_cable_wq); - ret = queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, delay_jiffies); - if (ret < 0) { - dev_err(&pdev->dev, - "failed to schedule delayed work for cable detect\n"); - goto err_extcon; - } return ret; -- cgit v1.2.3 From 369afd4ba22f5b8de0c9229b6e62b3f9e2207034 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 18 Apr 2014 16:47:30 +0200 Subject: extcon: max14577: Properly handle regmap_irq_get_virq error The regmap_irq_get_virq may return 0 or -EINVAL on error. Fail the probe in both situations. Signed-off-by: Krzysztof Kozlowski Cc: Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max14577.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 4179ce1a23de..2acf7461c905 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -721,7 +721,7 @@ static int max14577_muic_probe(struct platform_device *pdev) unsigned int virq = 0; virq = regmap_irq_get_virq(max14577->irq_data, muic_irq->irq); - if (!virq) + if (virq <= 0) return -EINVAL; muic_irq->virq = virq; -- cgit v1.2.3 From 1111244ff4493448c0ee66e814e20c6e81d3b93d Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:08 +0900 Subject: extcon: Add resource-managed extcon register function Add resource-managed extcon device register function for convenience. For example, if a extcon device is attached with new devm_extcon_dev_register(), that extcon device is automatically unregistered on driver detach. Signed-off-by: Sangjung Woo [Fix bug about devm_extcon_dev_match/release() and code clean by Chanwoo Choi] Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-class.c | 69 +++++++++++++++++++++++++++++++++++++++++++ include/linux/extcon.h | 13 ++++++++ 2 files changed, 82 insertions(+) diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c index 7ab21aa6eaa1..f6df68989651 100644 --- a/drivers/extcon/extcon-class.c +++ b/drivers/extcon/extcon-class.c @@ -819,6 +819,75 @@ void extcon_dev_unregister(struct extcon_dev *edev) } EXPORT_SYMBOL_GPL(extcon_dev_unregister); +static void devm_extcon_dev_unreg(struct device *dev, void *res) +{ + extcon_dev_unregister(*(struct extcon_dev **)res); +} + +static int devm_extcon_dev_match(struct device *dev, void *res, void *data) +{ + struct extcon_dev **r = res; + + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + + return *r == data; +} + +/** + * devm_extcon_dev_register() - Resource-managed extcon_dev_register() + * @dev: device to allocate extcon device + * @edev: the new extcon device to register + * + * Managed extcon_dev_register() function. If extcon device is attached with + * this function, that extcon device is automatically unregistered on driver + * detach. Internally this function calls extcon_dev_register() function. + * To get more information, refer that function. + * + * If extcon device is registered with this function and the device needs to be + * unregistered separately, devm_extcon_dev_unregister() should be used. + * + * Returns 0 if success or negaive error number if failure. + */ +int devm_extcon_dev_register(struct device *dev, struct extcon_dev *edev) +{ + struct extcon_dev **ptr; + int ret; + + ptr = devres_alloc(devm_extcon_dev_unreg, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = extcon_dev_register(edev); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = edev; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_register); + +/** + * devm_extcon_dev_unregister() - Resource-managed extcon_dev_unregister() + * @dev: device the extcon belongs to + * @edev: the extcon device to unregister + * + * Unregister extcon device that is registered with devm_extcon_dev_register() + * function. + */ +void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_unreg, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_unregister); + #ifdef CONFIG_OF /* * extcon_get_edev_by_phandle - Get the extcon device from devicetree diff --git a/include/linux/extcon.h b/include/linux/extcon.h index f488145bb2d4..548447be2d8f 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -185,6 +185,10 @@ struct extcon_specific_cable_nb { */ extern int extcon_dev_register(struct extcon_dev *edev); extern void extcon_dev_unregister(struct extcon_dev *edev); +extern int devm_extcon_dev_register(struct device *dev, + struct extcon_dev *edev); +extern void devm_extcon_dev_unregister(struct device *dev, + struct extcon_dev *edev); extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name); /* @@ -254,6 +258,15 @@ static inline int extcon_dev_register(struct extcon_dev *edev) static inline void extcon_dev_unregister(struct extcon_dev *edev) { } +static inline int devm_extcon_dev_register(struct device *dev, + struct extcon_dev *edev) +{ + return -EINVAL; +} + +static inline void devm_extcon_dev_unregister(struct device *dev, + struct extcon_dev *edev) { } + static inline u32 extcon_get_state(struct extcon_dev *edev) { return 0; -- cgit v1.2.3 From 4b5dd738837aaac9cca98db1a39dc5ba1c802e45 Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:09 +0900 Subject: extcon: adc-jack: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-adc-jack.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index e23f1c2e5053..549d8207aacf 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -105,9 +105,8 @@ static int adc_jack_probe(struct platform_device *pdev) data->edev.name = pdata->name; if (!pdata->cable_names) { - err = -EINVAL; dev_err(&pdev->dev, "error: cable_names not defined.\n"); - goto out; + return -EINVAL; } data->edev.dev.parent = &pdev->dev; @@ -117,18 +116,16 @@ static int adc_jack_probe(struct platform_device *pdev) for (i = 0; data->edev.supported_cable[i]; i++) ; if (i == 0 || i > SUPPORTED_CABLE_MAX) { - err = -EINVAL; dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", i - 1); - goto out; + return -EINVAL; } data->num_cables = i; if (!pdata->adc_conditions || !pdata->adc_conditions[0].state) { - err = -EINVAL; dev_err(&pdev->dev, "error: adc_conditions not defined.\n"); - goto out; + return -EINVAL; } data->adc_conditions = pdata->adc_conditions; @@ -138,10 +135,8 @@ static int adc_jack_probe(struct platform_device *pdev) data->num_conditions = i; data->chan = iio_channel_get(&pdev->dev, pdata->consumer_channel); - if (IS_ERR(data->chan)) { - err = PTR_ERR(data->chan); - goto out; - } + if (IS_ERR(data->chan)) + return PTR_ERR(data->chan); data->handling_delay = msecs_to_jiffies(pdata->handling_delay_ms); @@ -149,15 +144,14 @@ static int adc_jack_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - err = extcon_dev_register(&data->edev); + err = devm_extcon_dev_register(&pdev->dev, &data->edev); if (err) - goto out; + return err; data->irq = platform_get_irq(pdev, 0); if (!data->irq) { dev_err(&pdev->dev, "platform_get_irq failed\n"); - err = -ENODEV; - goto err_irq; + return -ENODEV; } err = request_any_context_irq(data->irq, adc_jack_irq_thread, @@ -165,15 +159,10 @@ static int adc_jack_probe(struct platform_device *pdev) if (err < 0) { dev_err(&pdev->dev, "error: irq %d\n", data->irq); - goto err_irq; + return err; } return 0; - -err_irq: - extcon_dev_unregister(&data->edev); -out: - return err; } static int adc_jack_remove(struct platform_device *pdev) @@ -182,7 +171,6 @@ static int adc_jack_remove(struct platform_device *pdev) free_irq(data->irq, data); cancel_work_sync(&data->handler.work); - extcon_dev_unregister(&data->edev); return 0; } -- cgit v1.2.3 From d92c2f12f8a940444f28795ccbfa845fc358963e Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:10 +0900 Subject: extcon: gpio: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-gpio.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 13d522255d81..43af34c4a518 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -121,34 +121,27 @@ static int gpio_extcon_probe(struct platform_device *pdev) msecs_to_jiffies(pdata->debounce); } - ret = extcon_dev_register(&extcon_data->edev); + ret = devm_extcon_dev_register(&pdev->dev, &extcon_data->edev); if (ret < 0) return ret; INIT_DELAYED_WORK(&extcon_data->work, gpio_extcon_work); extcon_data->irq = gpio_to_irq(extcon_data->gpio); - if (extcon_data->irq < 0) { - ret = extcon_data->irq; - goto err; - } + if (extcon_data->irq < 0) + return extcon_data->irq; ret = request_any_context_irq(extcon_data->irq, gpio_irq_handler, pdata->irq_flags, pdev->name, extcon_data); if (ret < 0) - goto err; + return ret; platform_set_drvdata(pdev, extcon_data); /* Perform initial detection */ gpio_extcon_work(&extcon_data->work.work); return 0; - -err: - extcon_dev_unregister(&extcon_data->edev); - - return ret; } static int gpio_extcon_remove(struct platform_device *pdev) @@ -157,7 +150,6 @@ static int gpio_extcon_remove(struct platform_device *pdev) cancel_delayed_work_sync(&extcon_data->work); free_irq(extcon_data->irq, extcon_data); - extcon_dev_unregister(&extcon_data->edev); return 0; } -- cgit v1.2.3 From 14dbd54962f9679b7573f853814b8ff15b6ec407 Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:11 +0900 Subject: extcon: max14577: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Reviewed-by: Krzysztof Kozlowski Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max14577.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index 2acf7461c905..d17dd4ff4d0f 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -747,7 +747,7 @@ static int max14577_muic_probe(struct platform_device *pdev) info->edev->name = dev_name(&pdev->dev); info->edev->supported_cable = max14577_extcon_cable; - ret = extcon_dev_register(info->edev); + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); return ret; @@ -766,7 +766,7 @@ static int max14577_muic_probe(struct platform_device *pdev) MAX14577_REG_DEVICEID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); - goto err_extcon; + return ret; } dev_info(info->dev, "device ID : 0x%x\n", id); @@ -786,10 +786,6 @@ static int max14577_muic_probe(struct platform_device *pdev) delay_jiffies); return ret; - -err_extcon: - extcon_dev_unregister(info->edev); - return ret; } static int max14577_muic_remove(struct platform_device *pdev) @@ -797,7 +793,6 @@ static int max14577_muic_remove(struct platform_device *pdev) struct max14577_muic_info *info = platform_get_drvdata(pdev); cancel_work_sync(&info->irq_work); - extcon_dev_unregister(info->edev); return 0; } -- cgit v1.2.3 From 10fae1184d00f09696078e572230402543ae34c8 Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:12 +0900 Subject: extcon: max77693: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Reviewed-by: Krzysztof Kozlowski Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max77693.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 39cd095d103c..f0f18e29ae07 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1185,7 +1185,7 @@ static int max77693_muic_probe(struct platform_device *pdev) info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; info->edev->supported_cable = max77693_extcon_cable; - ret = extcon_dev_register(info->edev); + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; @@ -1267,7 +1267,7 @@ static int max77693_muic_probe(struct platform_device *pdev) MAX77693_MUIC_REG_ID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); - goto err_extcon; + goto err_irq; } dev_info(info->dev, "device ID : 0x%x\n", id); @@ -1288,8 +1288,6 @@ static int max77693_muic_probe(struct platform_device *pdev) return ret; -err_extcon: - extcon_dev_unregister(info->edev); err_irq: while (--i >= 0) free_irq(muic_irqs[i].virq, info); @@ -1305,7 +1303,6 @@ static int max77693_muic_remove(struct platform_device *pdev) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); input_unregister_device(info->dock); - extcon_dev_unregister(info->edev); return 0; } -- cgit v1.2.3 From 2923803d4f69fd8e4ed85786b8888986b2682154 Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:13 +0900 Subject: extcon: max8997: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Reviewed-by: Krzysztof Kozlowski Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-max8997.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 223e6b068c98..804a446fecdc 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -709,7 +709,7 @@ static int max8997_muic_probe(struct platform_device *pdev) info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; info->edev->supported_cable = max8997_extcon_cable; - ret = extcon_dev_register(info->edev); + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; @@ -790,8 +790,6 @@ static int max8997_muic_remove(struct platform_device *pdev) free_irq(muic_irqs[i].virq, info); cancel_work_sync(&info->irq_work); - extcon_dev_unregister(info->edev); - return 0; } -- cgit v1.2.3 From b4dad55abd47a23ca2c445d771c64204f7b12b49 Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:14 +0900 Subject: extcon: palmas: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-palmas.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index 51db5bcef720..1a770e0ee9ae 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -192,7 +192,7 @@ static int palmas_usb_probe(struct platform_device *pdev) palmas_usb->edev.name = kstrdup(node->name, GFP_KERNEL); palmas_usb->edev.mutually_exclusive = mutually_exclusive; - status = extcon_dev_register(&palmas_usb->edev); + status = devm_extcon_dev_register(&pdev->dev, &palmas_usb->edev); if (status) { dev_err(&pdev->dev, "failed to register extcon device\n"); kfree(palmas_usb->edev.name); @@ -209,7 +209,8 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->id_irq, status); - goto fail_extcon; + kfree(palmas_usb->edev.name); + return status; } } @@ -223,26 +224,20 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->vbus_irq, status); - goto fail_extcon; + kfree(palmas_usb->edev.name); + return status; } } palmas_enable_irq(palmas_usb); device_set_wakeup_capable(&pdev->dev, true); return 0; - -fail_extcon: - extcon_dev_unregister(&palmas_usb->edev); - kfree(palmas_usb->edev.name); - - return status; } static int palmas_usb_remove(struct platform_device *pdev) { struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); - extcon_dev_unregister(&palmas_usb->edev); kfree(palmas_usb->edev.name); return 0; -- cgit v1.2.3 From d88cc36704e7583214220a6284a9923768b01d2d Mon Sep 17 00:00:00 2001 From: Sangjung Woo Date: Mon, 21 Apr 2014 19:10:15 +0900 Subject: extcon: arizona: Use devm_extcon_dev_register() Use the resource-managed extcon device register function (i.e. devm_extcon_dev_register()) instead of extcon_dev_register(). If extcon device is attached with this function, that extcon device is automatically unregistered on driver detach. That reduces tiresome managing code. Signed-off-by: Sangjung Woo Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 98a14f6143a7..f63fa6feadb5 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1105,15 +1105,14 @@ static int arizona_extcon_probe(struct platform_device *pdev) info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); if (!info) { dev_err(&pdev->dev, "Failed to allocate memory\n"); - ret = -ENOMEM; - goto err; + return -ENOMEM; } info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); if (IS_ERR(info->micvdd)) { ret = PTR_ERR(info->micvdd); dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); - goto err; + return ret; } mutex_init(&info->lock); @@ -1155,11 +1154,11 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->edev.dev.parent = arizona->dev; info->edev.supported_cable = arizona_cable; - ret = extcon_dev_register(&info->edev); + ret = devm_extcon_dev_register(&pdev->dev, &info->edev); if (ret < 0) { dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", ret); - goto err; + return ret; } info->input = devm_input_allocate_device(&pdev->dev); @@ -1410,8 +1409,6 @@ err_rise: err_input: err_register: pm_runtime_disable(&pdev->dev); - extcon_dev_unregister(&info->edev); -err: return ret; } @@ -1445,7 +1442,6 @@ static int arizona_extcon_remove(struct platform_device *pdev) regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_ANALOGUE, ARIZONA_JD1_ENA, 0); arizona_clk32k_disable(arizona); - extcon_dev_unregister(&info->edev); return 0; } -- cgit v1.2.3 From a9af65223b41cec60cd44fa95a93d10149deb143 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 24 Apr 2014 19:46:49 +0900 Subject: extcon: Add extcon_dev_allocate/free() to control the memory of extcon device This patch add APIs to control the extcon device on extcon provider driver. The extcon_dev_allocate() allocates the memory of extcon device and initializes supported cables. And then extcon_dev_free() decrement the reference of the device of extcon device and free the memory of the extcon device. This APIs must need to implement devm_extcon_dev_allocate()/free() APIs. Signed-off-by: Chanwoo Choi Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-class.c | 36 ++++++++++++++++++++++++++++++++++++ include/linux/extcon.h | 13 +++++++++++++ 2 files changed, 49 insertions(+) diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c index f6df68989651..654ed52e17c2 100644 --- a/drivers/extcon/extcon-class.c +++ b/drivers/extcon/extcon-class.c @@ -565,6 +565,42 @@ static void dummy_sysfs_dev_release(struct device *dev) { } +/* + * extcon_dev_allocate() - Allocate the memory of extcon device. + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function allocates the memory for extcon device without allocating + * memory in each extcon provider driver and initialize default setting for + * extcon device. + * + * Return the pointer of extcon device if success or ERR_PTR(err) if fail + */ +struct extcon_dev *extcon_dev_allocate(const char **supported_cable) +{ + struct extcon_dev *edev; + + edev = kzalloc(sizeof(*edev), GFP_KERNEL); + if (!edev) + return ERR_PTR(-ENOMEM); + + edev->max_supported = 0; + edev->supported_cable = supported_cable; + + return edev; +} + +/* + * extcon_dev_free() - Free the memory of extcon device. + * @edev: the extcon device to free + */ +void extcon_dev_free(struct extcon_dev *edev) +{ + kfree(edev); +} +EXPORT_SYMBOL_GPL(extcon_dev_free); + /** * extcon_dev_register() - Register a new extcon device * @edev : the new extcon device (should be allocated before calling) diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 548447be2d8f..15361a2f2f19 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -191,6 +191,12 @@ extern void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev); extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name); +/* + * Following APIs control the memory of extcon device. + */ +extern struct extcon_dev *extcon_dev_allocate(const char **cables); +extern void extcon_dev_free(struct extcon_dev *edev); + /* * get/set/update_state access the 32b encoded state value, which represents * states of all possible cables of the multistate port. For example, if one @@ -267,6 +273,13 @@ static inline int devm_extcon_dev_register(struct device *dev, static inline void devm_extcon_dev_unregister(struct device *dev, struct extcon_dev *edev) { } +static inline struct extcon_dev *extcon_dev_allocate(const char **cables) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void extcon_dev_free(struct extcon_dev *edev) { } + static inline u32 extcon_get_state(struct extcon_dev *edev) { return 0; -- cgit v1.2.3 From 739ba1bfdb15e773999aafddbd6c59b5737797a0 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 24 Apr 2014 20:12:15 +0900 Subject: extcon: Add devm_extcon_dev_allocate/free to manage the resource of extcon device This patch add device managed devm_extcon_dev_{allocate,free} to automatically free the memory of extcon_dev structure without handling free operation. Signed-off-by: Chanwoo Choi Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-class.c | 70 +++++++++++++++++++++++++++++++++++-------- include/linux/extcon.h | 11 +++++++ 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c index 654ed52e17c2..18d42c0e4581 100644 --- a/drivers/extcon/extcon-class.c +++ b/drivers/extcon/extcon-class.c @@ -601,6 +601,64 @@ void extcon_dev_free(struct extcon_dev *edev) } EXPORT_SYMBOL_GPL(extcon_dev_free); +static int devm_extcon_dev_match(struct device *dev, void *res, void *data) +{ + struct extcon_dev **r = res; + + if (WARN_ON(!r || !*r)) + return 0; + + return *r == data; +} + +static void devm_extcon_dev_release(struct device *dev, void *res) +{ + extcon_dev_free(*(struct extcon_dev **)res); +} + +/** + * devm_extcon_dev_allocate - Allocate managed extcon device + * @dev: device owning the extcon device being created + * @supported_cable: Array of supported cable names ending with NULL. + * If supported_cable is NULL, cable name related APIs + * are disabled. + * + * This function manages automatically the memory of extcon device using device + * resource management and simplify the control of freeing the memory of extcon + * device. + * + * Returns the pointer memory of allocated extcon_dev if success + * or ERR_PTR(err) if fail + */ +struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **supported_cable) +{ + struct extcon_dev **ptr, *edev; + + ptr = devres_alloc(devm_extcon_dev_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + edev = extcon_dev_allocate(supported_cable); + if (IS_ERR(edev)) { + devres_free(ptr); + return edev; + } + + *ptr = edev; + devres_add(dev, ptr); + + return edev; +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_allocate); + +void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev) +{ + WARN_ON(devres_release(dev, devm_extcon_dev_release, + devm_extcon_dev_match, edev)); +} +EXPORT_SYMBOL_GPL(devm_extcon_dev_free); + /** * extcon_dev_register() - Register a new extcon device * @edev : the new extcon device (should be allocated before calling) @@ -860,18 +918,6 @@ static void devm_extcon_dev_unreg(struct device *dev, void *res) extcon_dev_unregister(*(struct extcon_dev **)res); } -static int devm_extcon_dev_match(struct device *dev, void *res, void *data) -{ - struct extcon_dev **r = res; - - if (!r || !*r) { - WARN_ON(!r || !*r); - return 0; - } - - return *r == data; -} - /** * devm_extcon_dev_register() - Resource-managed extcon_dev_register() * @dev: device to allocate extcon device diff --git a/include/linux/extcon.h b/include/linux/extcon.h index 15361a2f2f19..36f49c405dfb 100644 --- a/include/linux/extcon.h +++ b/include/linux/extcon.h @@ -196,6 +196,9 @@ extern struct extcon_dev *extcon_get_extcon_dev(const char *extcon_name); */ extern struct extcon_dev *extcon_dev_allocate(const char **cables); extern void extcon_dev_free(struct extcon_dev *edev); +extern struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **cables); +extern void devm_extcon_dev_free(struct device *dev, struct extcon_dev *edev); /* * get/set/update_state access the 32b encoded state value, which represents @@ -280,6 +283,14 @@ static inline struct extcon_dev *extcon_dev_allocate(const char **cables) static inline void extcon_dev_free(struct extcon_dev *edev) { } +static inline struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, + const char **cables) +{ + return ERR_PTR(-ENOSYS); +} + +static inline void devm_extcon_dev_free(struct extcon_dev *edev) { } + static inline u32 extcon_get_state(struct extcon_dev *edev) { return 0; -- cgit v1.2.3 From 22f9afb95ba67eb6839e2b5499f5f42a767b1c18 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 21 Apr 2014 20:37:51 +0900 Subject: extcon: max8997: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Signed-off-by: Chanwoo Choi Reviewed-by: Krzysztof Kozlowski Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-max8997.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index 804a446fecdc..d9f7f1baaa03 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -699,16 +699,15 @@ static int max8997_muic_probe(struct platform_device *pdev) } /* External connector */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), - GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); ret = -ENOMEM; goto err_irq; } info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; - info->edev->supported_cable = max8997_extcon_cable; + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); -- cgit v1.2.3 From 577bef11048c9dc7ce5f9f18e89bf5305be49554 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 21 Apr 2014 20:39:53 +0900 Subject: extcon: max77693: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Signed-off-by: Chanwoo Choi Reviewed-by: Krzysztof Kozlowski Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-max77693.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index f0f18e29ae07..2c7c3e191591 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1175,23 +1175,22 @@ static int max77693_muic_probe(struct platform_device *pdev) } /* Initialize extcon device */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(struct extcon_dev), - GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max77693_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); ret = -ENOMEM; goto err_irq; } info->edev->name = DEV_NAME; info->edev->dev.parent = &pdev->dev; - info->edev->supported_cable = max77693_extcon_cable; + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); goto err_irq; } - /* Initialize MUIC register by using platform data or default data */ if (pdata && pdata->muic_data) { init_data = pdata->muic_data->init_data; -- cgit v1.2.3 From 0604002cde72cd60a11013daf2d9f456d4895ce8 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 24 Apr 2014 20:15:45 +0900 Subject: extcon: max14577: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Cc: Krzysztof Kozlowski Signed-off-by: Chanwoo Choi Reviewed-by: Krzysztof Kozlowski Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-max14577.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index d17dd4ff4d0f..d49e891b5675 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -739,14 +739,15 @@ static int max14577_muic_probe(struct platform_device *pdev) } /* Initialize extcon device */ - info->edev = devm_kzalloc(&pdev->dev, sizeof(*info->edev), GFP_KERNEL); - if (!info->edev) { + info->edev = devm_extcon_dev_allocate(&pdev->dev, + max14577_extcon_cable); + if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); return -ENOMEM; } info->edev->name = dev_name(&pdev->dev); - info->edev->supported_cable = max14577_extcon_cable; + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); -- cgit v1.2.3 From ef70a214b558184867e5943cf943a03293250ea7 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 21 Apr 2014 20:47:31 +0900 Subject: extcon: arizona: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Cc: Charles Keepax Cc: Mark Brown Cc: patches@opensource.wolfsonmicro.com Signed-off-by: Chanwoo Choi Acked-by: Charles Keepax Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-arizona.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index f63fa6feadb5..6c84e3d12043 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -91,7 +91,7 @@ struct arizona_extcon_info { int hpdet_ip; - struct extcon_dev edev; + struct extcon_dev *edev; }; static const struct arizona_micd_config micd_default_modes[] = { @@ -546,7 +546,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) } /* If the cable was removed while measuring ignore the result */ - ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); if (ret < 0) { dev_err(arizona->dev, "Failed to check cable state: %d\n", ret); @@ -581,7 +581,7 @@ static irqreturn_t arizona_hpdet_irq(int irq, void *data) else report = ARIZONA_CABLE_HEADPHONE; - ret = extcon_set_cable_state_(&info->edev, report, true); + ret = extcon_set_cable_state_(info->edev, report, true); if (ret != 0) dev_err(arizona->dev, "Failed to report HP/line: %d\n", ret); @@ -664,7 +664,7 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_HEADPHONE, 1 << ARIZONA_CABLE_HEADPHONE); if (ret != 0) @@ -723,7 +723,7 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_HEADPHONE, 1 << ARIZONA_CABLE_HEADPHONE); if (ret != 0) @@ -764,7 +764,7 @@ static void arizona_micd_detect(struct work_struct *work) mutex_lock(&info->lock); /* If the cable was removed while measuring ignore the result */ - ret = extcon_get_cable_state_(&info->edev, ARIZONA_CABLE_MECHANICAL); + ret = extcon_get_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL); if (ret < 0) { dev_err(arizona->dev, "Failed to check cable state: %d\n", ret); @@ -812,7 +812,7 @@ static void arizona_micd_detect(struct work_struct *work) if (info->detecting && (val & ARIZONA_MICD_LVL_8)) { arizona_identify_headphone(info); - ret = extcon_update_state(&info->edev, + ret = extcon_update_state(info->edev, 1 << ARIZONA_CABLE_MICROPHONE, 1 << ARIZONA_CABLE_MICROPHONE); @@ -999,7 +999,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (info->last_jackdet == present) { dev_dbg(arizona->dev, "Detected jack\n"); - ret = extcon_set_cable_state_(&info->edev, + ret = extcon_set_cable_state_(info->edev, ARIZONA_CABLE_MECHANICAL, true); if (ret != 0) @@ -1038,7 +1038,7 @@ static irqreturn_t arizona_jackdet(int irq, void *data) info->micd_ranges[i].key, 0); input_sync(info->input); - ret = extcon_update_state(&info->edev, 0xffffffff, 0); + ret = extcon_update_state(info->edev, 0xffffffff, 0); if (ret != 0) dev_err(arizona->dev, "Removal report failed: %d\n", ret); @@ -1150,11 +1150,15 @@ static int arizona_extcon_probe(struct platform_device *pdev) break; } - info->edev.name = "Headset Jack"; - info->edev.dev.parent = arizona->dev; - info->edev.supported_cable = arizona_cable; + info->edev = devm_extcon_dev_allocate(&pdev->dev, arizona_cable); + if (IS_ERR(info->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + info->edev->name = "Headset Jack"; + info->edev->dev.parent = arizona->dev; - ret = devm_extcon_dev_register(&pdev->dev, &info->edev); + ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret < 0) { dev_err(arizona->dev, "extcon_dev_register() failed: %d\n", ret); -- cgit v1.2.3 From 1876fd9af59904078a73bdd4283c3924fd6cf18e Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 21 Apr 2014 20:49:30 +0900 Subject: extcon: adc-jack: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Signed-off-by: Chanwoo Choi Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-adc-jack.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index 549d8207aacf..e18f95be3733 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -39,7 +39,7 @@ * @chan: iio channel being queried. */ struct adc_jack_data { - struct extcon_dev edev; + struct extcon_dev *edev; const char **cable_names; int num_cables; @@ -64,7 +64,7 @@ static void adc_jack_handler(struct work_struct *work) ret = iio_read_channel_raw(data->chan, &adc_val); if (ret < 0) { - dev_err(&data->edev.dev, "read channel() error: %d\n", ret); + dev_err(&data->edev->dev, "read channel() error: %d\n", ret); return; } @@ -80,7 +80,7 @@ static void adc_jack_handler(struct work_struct *work) } /* if no def has met, it means state = 0 (no cables attached) */ - extcon_set_state(&data->edev, state); + extcon_set_state(data->edev, state); } static irqreturn_t adc_jack_irq_thread(int irq, void *_data) @@ -102,18 +102,21 @@ static int adc_jack_probe(struct platform_device *pdev) if (!data) return -ENOMEM; - data->edev.name = pdata->name; - if (!pdata->cable_names) { dev_err(&pdev->dev, "error: cable_names not defined.\n"); return -EINVAL; } - data->edev.dev.parent = &pdev->dev; - data->edev.supported_cable = pdata->cable_names; + data->edev = devm_extcon_dev_allocate(&pdev->dev, pdata->cable_names); + if (IS_ERR(data->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + data->edev->dev.parent = &pdev->dev; + data->edev->name = pdata->name; /* Check the length of array and set num_cables */ - for (i = 0; data->edev.supported_cable[i]; i++) + for (i = 0; data->edev->supported_cable[i]; i++) ; if (i == 0 || i > SUPPORTED_CABLE_MAX) { dev_err(&pdev->dev, "error: pdata->cable_names size = %d\n", @@ -144,7 +147,7 @@ static int adc_jack_probe(struct platform_device *pdev) platform_set_drvdata(pdev, data); - err = devm_extcon_dev_register(&pdev->dev, &data->edev); + err = devm_extcon_dev_register(&pdev->dev, data->edev); if (err) return err; -- cgit v1.2.3 From 60cd62d4f7d681a99eccf49941229ccf3a0069cf Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 21 Apr 2014 20:51:08 +0900 Subject: extcon: gpio: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Signed-off-by: Chanwoo Choi Reviewed-by: Felipe Balbi --- drivers/extcon/extcon-gpio.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 43af34c4a518..645b28356819 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -32,7 +32,7 @@ #include struct gpio_extcon_data { - struct extcon_dev edev; + struct extcon_dev *edev; unsigned gpio; bool gpio_active_low; const char *state_on; @@ -53,7 +53,7 @@ static void gpio_extcon_work(struct work_struct *work) state = gpio_get_value(data->gpio); if (data->gpio_active_low) state = !state; - extcon_set_state(&data->edev, state); + extcon_set_state(data->edev, state); } static irqreturn_t gpio_irq_handler(int irq, void *dev_id) @@ -67,9 +67,10 @@ static irqreturn_t gpio_irq_handler(int irq, void *dev_id) static ssize_t extcon_gpio_print_state(struct extcon_dev *edev, char *buf) { - struct gpio_extcon_data *extcon_data = - container_of(edev, struct gpio_extcon_data, edev); + struct device *dev = edev->dev.parent; + struct gpio_extcon_data *extcon_data = dev_get_drvdata(dev); const char *state; + if (extcon_get_state(edev)) state = extcon_data->state_on; else @@ -98,15 +99,21 @@ static int gpio_extcon_probe(struct platform_device *pdev) if (!extcon_data) return -ENOMEM; - extcon_data->edev.name = pdata->name; - extcon_data->edev.dev.parent = &pdev->dev; + extcon_data->edev = devm_extcon_dev_allocate(&pdev->dev, NULL); + if (IS_ERR(extcon_data->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + extcon_data->edev->name = pdata->name; + extcon_data->edev->dev.parent = &pdev->dev; + extcon_data->gpio = pdata->gpio; extcon_data->gpio_active_low = pdata->gpio_active_low; extcon_data->state_on = pdata->state_on; extcon_data->state_off = pdata->state_off; extcon_data->check_on_resume = pdata->check_on_resume; if (pdata->state_on && pdata->state_off) - extcon_data->edev.print_state = extcon_gpio_print_state; + extcon_data->edev->print_state = extcon_gpio_print_state; ret = devm_gpio_request_one(&pdev->dev, extcon_data->gpio, GPIOF_DIR_IN, pdev->name); @@ -121,7 +128,7 @@ static int gpio_extcon_probe(struct platform_device *pdev) msecs_to_jiffies(pdata->debounce); } - ret = devm_extcon_dev_register(&pdev->dev, &extcon_data->edev); + ret = devm_extcon_dev_register(&pdev->dev, extcon_data->edev); if (ret < 0) return ret; -- cgit v1.2.3 From 3f79a3fb5f41e8f2229e5bf8aa725eaa79686f14 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Mon, 21 Apr 2014 20:44:53 +0900 Subject: extcon: palmas: Use devm_extcon_dev_allocate for extcon_dev This patch use devm_extcon_dev_allocate() to simplify the memory control of extcon device. Cc: Graeme Gregory Cc: Kishon Vijay Abraham I Cc: Felipe Balbi Cc: Samuel Ortiz Cc: Lee Jones Signed-off-by: Chanwoo Choi Acked-by: Lee Jones Acked-by: Felipe Balbi Tested-by: Felipe Balbi --- drivers/extcon/extcon-palmas.c | 35 ++++++++++++++++++++--------------- include/linux/mfd/palmas.h | 2 +- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index 1a770e0ee9ae..7417ce84eb2d 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -57,7 +57,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) if (vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS) { if (palmas_usb->linkstat != PALMAS_USB_STATE_VBUS) { palmas_usb->linkstat = PALMAS_USB_STATE_VBUS; - extcon_set_cable_state(&palmas_usb->edev, "USB", true); + extcon_set_cable_state(palmas_usb->edev, "USB", true); dev_info(palmas_usb->dev, "USB cable is attached\n"); } else { dev_dbg(palmas_usb->dev, @@ -66,7 +66,7 @@ static irqreturn_t palmas_vbus_irq_handler(int irq, void *_palmas_usb) } else if (!(vbus_line_state & PALMAS_INT3_LINE_STATE_VBUS)) { if (palmas_usb->linkstat == PALMAS_USB_STATE_VBUS) { palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB", false); + extcon_set_cable_state(palmas_usb->edev, "USB", false); dev_info(palmas_usb->dev, "USB cable is detached\n"); } else { dev_dbg(palmas_usb->dev, @@ -93,7 +93,7 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) PALMAS_USB_ID_INT_LATCH_CLR, PALMAS_USB_ID_INT_EN_HI_CLR_ID_GND); palmas_usb->linkstat = PALMAS_USB_STATE_ID; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); dev_info(palmas_usb->dev, "USB-HOST cable is attached\n"); } else if ((set & PALMAS_USB_ID_INT_SRC_ID_FLOAT) && (id_src & PALMAS_USB_ID_INT_SRC_ID_FLOAT)) { @@ -101,17 +101,17 @@ static irqreturn_t palmas_id_irq_handler(int irq, void *_palmas_usb) PALMAS_USB_ID_INT_LATCH_CLR, PALMAS_USB_ID_INT_EN_HI_CLR_ID_FLOAT); palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_ID) && (!(set & PALMAS_USB_ID_INT_SRC_ID_GND))) { palmas_usb->linkstat = PALMAS_USB_STATE_DISCONNECT; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", false); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", false); dev_info(palmas_usb->dev, "USB-HOST cable is detached\n"); } else if ((palmas_usb->linkstat == PALMAS_USB_STATE_DISCONNECT) && (id_src & PALMAS_USB_ID_INT_SRC_ID_GND)) { palmas_usb->linkstat = PALMAS_USB_STATE_ID; - extcon_set_cable_state(&palmas_usb->edev, "USB-HOST", true); + extcon_set_cable_state(palmas_usb->edev, "USB-HOST", true); dev_info(palmas_usb->dev, " USB-HOST cable is attached\n"); } @@ -187,15 +187,20 @@ static int palmas_usb_probe(struct platform_device *pdev) platform_set_drvdata(pdev, palmas_usb); - palmas_usb->edev.supported_cable = palmas_extcon_cable; - palmas_usb->edev.dev.parent = palmas_usb->dev; - palmas_usb->edev.name = kstrdup(node->name, GFP_KERNEL); - palmas_usb->edev.mutually_exclusive = mutually_exclusive; + palmas_usb->edev = devm_extcon_dev_allocate(&pdev->dev, + palmas_extcon_cable); + if (IS_ERR(palmas_usb->edev)) { + dev_err(&pdev->dev, "failed to allocate extcon device\n"); + return -ENOMEM; + } + palmas_usb->edev->name = kstrdup(node->name, GFP_KERNEL); + palmas_usb->edev->dev.parent = palmas_usb->dev; + palmas_usb->edev->mutually_exclusive = mutually_exclusive; - status = devm_extcon_dev_register(&pdev->dev, &palmas_usb->edev); + status = devm_extcon_dev_register(&pdev->dev, palmas_usb->edev); if (status) { dev_err(&pdev->dev, "failed to register extcon device\n"); - kfree(palmas_usb->edev.name); + kfree(palmas_usb->edev->name); return status; } @@ -209,7 +214,7 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->id_irq, status); - kfree(palmas_usb->edev.name); + kfree(palmas_usb->edev->name); return status; } } @@ -224,7 +229,7 @@ static int palmas_usb_probe(struct platform_device *pdev) if (status < 0) { dev_err(&pdev->dev, "can't get IRQ %d, err %d\n", palmas_usb->vbus_irq, status); - kfree(palmas_usb->edev.name); + kfree(palmas_usb->edev->name); return status; } } @@ -238,7 +243,7 @@ static int palmas_usb_remove(struct platform_device *pdev) { struct palmas_usb *palmas_usb = platform_get_drvdata(pdev); - kfree(palmas_usb->edev.name); + kfree(palmas_usb->edev->name); return 0; } diff --git a/include/linux/mfd/palmas.h b/include/linux/mfd/palmas.h index 9974e387e483..b8f87b704409 100644 --- a/include/linux/mfd/palmas.h +++ b/include/linux/mfd/palmas.h @@ -415,7 +415,7 @@ struct palmas_usb { struct palmas *palmas; struct device *dev; - struct extcon_dev edev; + struct extcon_dev *edev; int id_otg_irq; int id_irq; -- cgit v1.2.3 From 4fcbc99b7565f915bea58e14b5e6f089bf9abf16 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:51:55 +0200 Subject: mei: implement power gating isolation hbm layer Add send message functions and receive dispatch stubs for power gating isolation hbm protocol. The protocol consist of requests for entering and exiting the power gating isolation state and their responses. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 41 +++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/hbm.h | 1 + drivers/misc/mei/hw.h | 16 ++++++++++++++++ drivers/misc/mei/init.c | 1 + drivers/misc/mei/mei_dev.h | 1 + 5 files changed, 60 insertions(+) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index 4960288e543a..a16b47c855aa 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -14,6 +14,7 @@ * */ +#include #include #include #include @@ -289,6 +290,34 @@ static int mei_hbm_prop_req(struct mei_device *dev) return 0; } +/* + * mei_hbm_pg - sends pg command + * + * @dev: the device structure + * @pg_cmd: the pg command code + * + * This function returns -EIO on write failure + */ +int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd) +{ + struct mei_msg_hdr *mei_hdr = &dev->wr_msg.hdr; + struct hbm_power_gate *req; + const size_t len = sizeof(struct hbm_power_gate); + int ret; + + mei_hbm_hdr(mei_hdr, len); + + req = (struct hbm_power_gate *)dev->wr_msg.data; + memset(req, 0, len); + req->hbm_cmd = pg_cmd; + + ret = mei_write_message(dev, mei_hdr, dev->wr_msg.data); + if (ret) + dev_err(&dev->pdev->dev, "power gate command write failed.\n"); + return ret; +} +EXPORT_SYMBOL_GPL(mei_hbm_pg); + /** * mei_hbm_stop_req - send stop request message * @@ -701,6 +730,18 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) mei_hbm_cl_flow_control_res(dev, flow_control); break; + case MEI_PG_ISOLATION_ENTRY_RES_CMD: + dev_dbg(&dev->pdev->dev, "power gate isolation entry response received\n"); + if (waitqueue_active(&dev->wait_pg)) + wake_up(&dev->wait_pg); + break; + + case MEI_PG_ISOLATION_EXIT_REQ_CMD: + dev_dbg(&dev->pdev->dev, "power gate isolation exit request received\n"); + if (waitqueue_active(&dev->wait_pg)) + wake_up(&dev->wait_pg); + break; + case HOST_CLIENT_PROPERTIES_RES_CMD: dev_dbg(&dev->pdev->dev, "hbm: properties response: message received.\n"); diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h index 20e8782711c0..8e39cee408d0 100644 --- a/drivers/misc/mei/hbm.h +++ b/drivers/misc/mei/hbm.h @@ -57,6 +57,7 @@ int mei_hbm_cl_disconnect_req(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_disconnect_rsp(struct mei_device *dev, struct mei_cl *cl); int mei_hbm_cl_connect_req(struct mei_device *dev, struct mei_cl *cl); bool mei_hbm_version_is_supported(struct mei_device *dev); +int mei_hbm_pg(struct mei_device *dev, u8 pg_cmd); #endif /* _MEI_HBM_H_ */ diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 6b476ab49b2e..1d70968d90b6 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -69,6 +69,11 @@ #define MEI_FLOW_CONTROL_CMD 0x08 +#define MEI_PG_ISOLATION_ENTRY_REQ_CMD 0x0a +#define MEI_PG_ISOLATION_ENTRY_RES_CMD 0x8a +#define MEI_PG_ISOLATION_EXIT_REQ_CMD 0x0b +#define MEI_PG_ISOLATION_EXIT_RES_CMD 0x8b + /* * MEI Stop Reason * used by hbm_host_stop_request.reason @@ -207,6 +212,17 @@ struct hbm_props_response { struct mei_client_properties client_properties; } __packed; +/** + * struct hbm_power_gate - power gate request/response + * + * @hbm_cmd - bus message command header + * @reserved[3] + */ +struct hbm_power_gate { + u8 hbm_cmd; + u8 reserved[3]; +} __packed; + /** * struct hbm_client_connect_request - connect/disconnect request * diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 4460975c0eef..cc604e1d9457 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -312,6 +312,7 @@ void mei_device_init(struct mei_device *dev) INIT_LIST_HEAD(&dev->device_list); mutex_init(&dev->device_lock); init_waitqueue_head(&dev->wait_hw_ready); + init_waitqueue_head(&dev->wait_pg); init_waitqueue_head(&dev->wait_recvd_msg); init_waitqueue_head(&dev->wait_stop_wd); dev->dev_state = MEI_DEV_INITIALIZING; diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 94a516716d22..4d4c041a7e4f 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -371,6 +371,7 @@ struct mei_device { * waiting queue for receive message from FW */ wait_queue_head_t wait_hw_ready; + wait_queue_head_t wait_pg; wait_queue_head_t wait_recvd_msg; wait_queue_head_t wait_stop_wd; -- cgit v1.2.3 From ad4d355b4b9764373baaf9e8de6613919e1342fa Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:51:56 +0200 Subject: mei: me: introduce power gating registers LPT devices have internal power gating handled through registers and hbm calls Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me-regs.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index cabc04383685..a7856c0ac576 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -133,6 +133,8 @@ #define ME_CB_RW 8 /* ME_CSR_HA - ME Control Status Host Access register (read only) */ #define ME_CSR_HA 0xC +/* H_HGC_CSR - PGI register */ +#define H_HPG_CSR 0x10 /* register bits of H_CSR (Host Control Status register) */ @@ -162,6 +164,8 @@ access to ME_CBD */ #define ME_CBWP_HRA 0x00FF0000 /* ME CB Read Pointer HRA - host read only access to ME_CBRP */ #define ME_CBRP_HRA 0x0000FF00 +/* ME Power Gate Isolation Capability HRA - host ready only access */ +#define ME_PGIC_HRA 0x00000040 /* ME Reset HRA - host read only access to ME_RST */ #define ME_RST_HRA 0x00000010 /* ME Ready HRA - host read only access to ME_RDY */ @@ -173,4 +177,9 @@ access to ME_CBD */ /* ME Interrupt Enable HRA - host read only access to ME_IE */ #define ME_IE_HRA 0x00000001 + +/* register bits - H_HPG_CSR */ +#define H_HPG_CSR_PGIHEXR 0x00000001 +#define H_HPG_CSR_PGI 0x00000002 + #endif /* _MEI_HW_MEI_REGS_H_ */ -- cgit v1.2.3 From b16c35716b843acdbe562bc0068580c50db203ff Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:51:57 +0200 Subject: mei: me: add power gating isolation register write wrappers Add entry and exit power gating isolation register write handler. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 8dbdaaef1af5..fc5d001983b3 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -430,6 +430,35 @@ static int mei_me_read_slots(struct mei_device *dev, unsigned char *buffer, return 0; } +/** + * mei_me_pg_enter - write pg enter register to mei device. + * + * @dev: the device structure + */ +static void mei_me_pg_enter(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + reg |= H_HPG_CSR_PGI; + mei_me_reg_write(hw, H_HPG_CSR, reg); +} + +/** + * mei_me_pg_enter - write pg enter register to mei device. + * + * @dev: the device structure + */ +static void mei_me_pg_exit(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, H_HPG_CSR); + + WARN(!(reg & H_HPG_CSR_PGI), "PGI is not set\n"); + + reg |= H_HPG_CSR_PGIHEXR; + mei_me_reg_write(hw, H_HPG_CSR, reg); +} + /** * mei_me_irq_quick_handler - The ISR of the MEI device * -- cgit v1.2.3 From ee7e5afd2c369b64ffcf419d38ce7ad1c709a53e Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:51:58 +0200 Subject: mei: condition PGI support on HW and HBM version Enable power gating isolation only if hw and fw support it. This is indicated by ME_PGIC_HRA bit in ME_CSR_HA register and on HBM protocol version. The information is exported to MEI layer through new pg_is_enabled hw op. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 37 +++++++++++++++++++++++++++++++++++++ drivers/misc/mei/hw-txe.c | 14 ++++++++++++++ drivers/misc/mei/hw.h | 6 ++++++ drivers/misc/mei/mei_dev.h | 10 ++++++++++ 4 files changed, 67 insertions(+) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index fc5d001983b3..7a7e66250dfd 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -459,6 +459,41 @@ static void mei_me_pg_exit(struct mei_device *dev) mei_me_reg_write(hw, H_HPG_CSR, reg); } +/** + * mei_me_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_me_pg_is_enabled(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + u32 reg = mei_me_reg_read(hw, ME_CSR_HA); + + if ((reg & ME_PGIC_HRA) == 0) + goto notsupported; + + if (dev->version.major_version < HBM_MAJOR_VERSION_PGI) + goto notsupported; + + if (dev->version.major_version == HBM_MAJOR_VERSION_PGI && + dev->version.minor_version < HBM_MINOR_VERSION_PGI) + goto notsupported; + + return true; + +notsupported: + dev_dbg(&dev->pdev->dev, "pg: not supported: HGP = %d hbm version %d.%d ?= %d.%d\n", + !!(reg & ME_PGIC_HRA), + dev->version.major_version, + dev->version.minor_version, + HBM_MAJOR_VERSION_PGI, + HBM_MINOR_VERSION_PGI); + + return false; +} + /** * mei_me_irq_quick_handler - The ISR of the MEI device * @@ -573,6 +608,8 @@ static const struct mei_hw_ops mei_me_hw_ops = { .hw_config = mei_me_hw_config, .hw_start = mei_me_hw_start, + .pg_is_enabled = mei_me_pg_is_enabled, + .intr_clear = mei_me_intr_clear, .intr_enable = mei_me_intr_enable, .intr_disable = mei_me_intr_disable, diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index f60182a52f96..49f197a956c9 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -279,6 +279,18 @@ int mei_txe_aliveness_set_sync(struct mei_device *dev, u32 req) return 0; } +/** + * mei_txe_pg_is_enabled - detect if PG is supported by HW + * + * @dev: the device structure + * + * returns: true is pg supported, false otherwise + */ +static bool mei_txe_pg_is_enabled(struct mei_device *dev) +{ + return true; +} + /** * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt * @@ -1017,6 +1029,8 @@ static const struct mei_hw_ops mei_txe_hw_ops = { .hw_config = mei_txe_hw_config, .hw_start = mei_txe_hw_start, + .pg_is_enabled = mei_txe_pg_is_enabled, + .intr_clear = mei_txe_intr_clear, .intr_enable = mei_txe_intr_enable, .intr_disable = mei_txe_intr_disable, diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 1d70968d90b6..ec4a91a534e6 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -39,6 +39,12 @@ #define HBM_MINOR_VERSION 0 #define HBM_MAJOR_VERSION 1 +/* + * MEI version with PGI support + */ +#define HBM_MINOR_VERSION_PGI 1 +#define HBM_MAJOR_VERSION_PGI 1 + /* Host bus message command opcode */ #define MEI_HBM_CMD_OP_MSK 0x7f /* Host bus message command RESPONSE */ diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 4d4c041a7e4f..ca7581ce0722 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -220,6 +220,8 @@ struct mei_cl { * @hw_start - start hw after reset * @hw_config - configure hw + * @pg_is_enabled - is power gating enabled + * @intr_clear - clear pending interrupts * @intr_enable - enable interrupts * @intr_disable - disable interrupts @@ -244,6 +246,8 @@ struct mei_hw_ops { int (*hw_start)(struct mei_device *dev); void (*hw_config)(struct mei_device *dev); + bool (*pg_is_enabled)(struct mei_device *dev); + void (*intr_clear)(struct mei_device *dev); void (*intr_enable)(struct mei_device *dev); void (*intr_disable)(struct mei_device *dev); @@ -558,6 +562,12 @@ static inline void mei_hw_config(struct mei_device *dev) { dev->ops->hw_config(dev); } + +static inline bool mei_pg_is_enabled(struct mei_device *dev) +{ + return dev->ops->pg_is_enabled(dev); +} + static inline int mei_hw_reset(struct mei_device *dev, bool enable) { return dev->ops->hw_reset(dev, enable); -- cgit v1.2.3 From 964a2331e9a207fc15ef9eef833212347498bea1 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:51:59 +0200 Subject: mei: expose hardware power gating state to mei layer Since the runtime pm and the internal power gating cannot be in complete sync in regards to I/O operations, we need to expose the device hardware internal power gating state to mei layer 2. We add pg_state handler that translate the hw internal pg state to mei layer 2. We add power gating event variable to keep power track of power gating transitions Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Reviewed-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 15 +++++++++++++++ drivers/misc/mei/hw-txe.c | 43 ++++++++++++++++++++++++++++++++----------- drivers/misc/mei/hw-txe.h | 14 ++++++-------- drivers/misc/mei/init.c | 2 ++ drivers/misc/mei/mei_dev.h | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 93 insertions(+), 19 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 7a7e66250dfd..02f3b0c3a2a3 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -113,6 +113,19 @@ static void mei_me_hw_config(struct mei_device *dev) /* Doesn't change in runtime */ dev->hbuf_depth = (hcsr & H_CBD) >> 24; } + +/** + * mei_me_pg_state - translate internal pg state + * to the mei power gating state + * + * @hw - me hardware + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_me_pg_state(struct mei_device *dev) +{ + return MEI_PG_OFF; +} + /** * mei_clear_interrupts - clear and stop interrupts * @@ -601,6 +614,8 @@ end: } static const struct mei_hw_ops mei_me_hw_ops = { + .pg_state = mei_me_pg_state, + .host_is_ready = mei_me_host_is_ready, .hw_is_ready = mei_me_hw_is_ready, diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 49f197a956c9..4a6f567f4f80 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -158,7 +158,7 @@ static bool mei_txe_aliveness_set(struct mei_device *dev, u32 req) dev_dbg(&dev->pdev->dev, "Aliveness current=%d request=%d\n", hw->aliveness, req); if (do_req) { - hw->recvd_aliveness = false; + dev->pg_event = MEI_PG_EVENT_WAIT; mei_txe_br_reg_write(hw, SICR_HOST_ALIVENESS_REQ_REG, req); } return do_req; @@ -213,6 +213,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) do { hw->aliveness = mei_txe_aliveness_get(dev); if (hw->aliveness == expected) { + dev->pg_event = MEI_PG_EVENT_IDLE; dev_dbg(&dev->pdev->dev, "aliveness settled after %d msecs\n", t); return t; @@ -223,6 +224,7 @@ static int mei_txe_aliveness_poll(struct mei_device *dev, u32 expected) t += MSEC_PER_SEC / 5; } while (t < SEC_ALIVENESS_WAIT_TIMEOUT); + dev->pg_event = MEI_PG_EVENT_IDLE; dev_err(&dev->pdev->dev, "aliveness timed out\n"); return -ETIME; } @@ -249,19 +251,22 @@ static int mei_txe_aliveness_wait(struct mei_device *dev, u32 expected) return 0; mutex_unlock(&dev->device_lock); - err = wait_event_timeout(hw->wait_aliveness, - hw->recvd_aliveness, timeout); + err = wait_event_timeout(hw->wait_aliveness_resp, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); mutex_lock(&dev->device_lock); hw->aliveness = mei_txe_aliveness_get(dev); ret = hw->aliveness == expected ? 0 : -ETIME; if (ret) - dev_err(&dev->pdev->dev, "aliveness timed out"); + dev_warn(&dev->pdev->dev, "aliveness timed out = %ld aliveness = %d event = %d\n", + err, hw->aliveness, dev->pg_event); else - dev_dbg(&dev->pdev->dev, "aliveness settled after %d msecs\n", - jiffies_to_msecs(timeout - err)); - hw->recvd_aliveness = false; + dev_dbg(&dev->pdev->dev, "aliveness settled after = %d msec aliveness = %d event = %d\n", + jiffies_to_msecs(timeout - err), + hw->aliveness, dev->pg_event); + + dev->pg_event = MEI_PG_EVENT_IDLE; return ret; } @@ -291,6 +296,20 @@ static bool mei_txe_pg_is_enabled(struct mei_device *dev) return true; } +/** + * mei_txe_pg_state - translate aliveness register value + * to the mei power gating state + * + * @dev: the device structure + * + * returns: MEI_PG_OFF if aliveness is on and MEI_PG_ON otherwise + */ +static inline enum mei_pg_state mei_txe_pg_state(struct mei_device *dev) +{ + struct mei_txe_hw *hw = to_txe_hw(dev); + return hw->aliveness ? MEI_PG_OFF : MEI_PG_ON; +} + /** * mei_txe_input_ready_interrupt_enable - sets the Input Ready Interrupt * @@ -972,9 +991,9 @@ irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id) /* Clear the interrupt cause */ dev_dbg(&dev->pdev->dev, "Aliveness Interrupt: Status: %d\n", hw->aliveness); - hw->recvd_aliveness = true; - if (waitqueue_active(&hw->wait_aliveness)) - wake_up(&hw->wait_aliveness); + dev->pg_event = MEI_PG_EVENT_RECEIVED; + if (waitqueue_active(&hw->wait_aliveness_resp)) + wake_up(&hw->wait_aliveness_resp); } @@ -1024,6 +1043,8 @@ static const struct mei_hw_ops mei_txe_hw_ops = { .host_is_ready = mei_txe_host_is_ready, + .pg_state = mei_txe_pg_state, + .hw_is_ready = mei_txe_hw_is_ready, .hw_reset = mei_txe_hw_reset, .hw_config = mei_txe_hw_config, @@ -1069,7 +1090,7 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) hw = to_txe_hw(dev); - init_waitqueue_head(&hw->wait_aliveness); + init_waitqueue_head(&hw->wait_aliveness_resp); dev->ops = &mei_txe_hw_ops; diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h index 0812d98633a4..799f9f25f424 100644 --- a/drivers/misc/mei/hw-txe.h +++ b/drivers/misc/mei/hw-txe.h @@ -35,12 +35,11 @@ /** * struct mei_txe_hw - txe hardware specifics * - * @mem_addr: SeC and BRIDGE bars - * @aliveness: aliveness (power gating) state of the hardware - * @readiness: readiness state of the hardware - * @wait_aliveness: aliveness wait queue - * @recvd_aliveness: aliveness interrupt was recived - * @intr_cause: translated interrupt cause + * @mem_addr: SeC and BRIDGE bars + * @aliveness: aliveness (power gating) state of the hardware + * @readiness: readiness state of the hardware + * @wait_aliveness_resp: aliveness wait queue + * @intr_cause: translated interrupt cause */ struct mei_txe_hw { void __iomem *mem_addr[NUM_OF_MEM_BARS]; @@ -48,8 +47,7 @@ struct mei_txe_hw { u32 readiness; u32 slots; - wait_queue_head_t wait_aliveness; - bool recvd_aliveness; + wait_queue_head_t wait_aliveness_resp; unsigned long intr_cause; }; diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index cc604e1d9457..4b1eb37344cb 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -341,6 +341,8 @@ void mei_device_init(struct mei_device *dev) * 0: Reserved for MEI Bus Message communications */ bitmap_set(dev->host_clients_map, 0, 1); + + dev->pg_event = MEI_PG_EVENT_IDLE; } EXPORT_SYMBOL_GPL(mei_device_init); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index ca7581ce0722..bec6adef68bd 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -220,6 +220,7 @@ struct mei_cl { * @hw_start - start hw after reset * @hw_config - configure hw + * @pg_state - power gating state of the device * @pg_is_enabled - is power gating enabled * @intr_clear - clear pending interrupts @@ -246,6 +247,7 @@ struct mei_hw_ops { int (*hw_start)(struct mei_device *dev); void (*hw_config)(struct mei_device *dev); + enum mei_pg_state (*pg_state)(struct mei_device *dev); bool (*pg_is_enabled)(struct mei_device *dev); void (*intr_clear)(struct mei_device *dev); @@ -335,11 +337,37 @@ struct mei_cl_device { void *priv_data; }; + + /** + * enum mei_pg_event - power gating transition events + * + * @MEI_PG_EVENT_IDLE: the driver is not in power gating transition + * @MEI_PG_EVENT_WAIT: the driver is waiting for a pg event to complete + * @MEI_PG_EVENT_RECEIVED: the driver received pg event + */ +enum mei_pg_event { + MEI_PG_EVENT_IDLE, + MEI_PG_EVENT_WAIT, + MEI_PG_EVENT_RECEIVED, +}; + +/** + * enum mei_pg_state - device internal power gating state + * + * @MEI_PG_OFF: device is not power gated - it is active + * @MEI_PG_ON: device is power gated - it is in lower power state + */ +enum mei_pg_state { + MEI_PG_OFF = 0, + MEI_PG_ON = 1, +}; + /** * struct mei_device - MEI private device struct * @reset_count - limits the number of consecutive resets * @hbm_state - state of host bus message protocol + * @pg_event - power gating event * @mem_addr - mem mapped base register address * @hbuf_depth - depth of hardware host/write buffer is slots @@ -387,6 +415,11 @@ struct mei_device { enum mei_hbm_state hbm_state; u16 init_clients_timer; + /* + * Power Gating support + */ + enum mei_pg_event pg_event; + unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; @@ -563,6 +596,11 @@ static inline void mei_hw_config(struct mei_device *dev) dev->ops->hw_config(dev); } +static inline enum mei_pg_state mei_pg_state(struct mei_device *dev) +{ + return dev->ops->pg_state(dev); +} + static inline bool mei_pg_is_enabled(struct mei_device *dev) { return dev->ops->pg_is_enabled(dev); -- cgit v1.2.3 From ba9cdd0e1ad88004c68395697fb2ec6b9b4ff020 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:52:00 +0200 Subject: mei: me: add pg exit and entry flow commands For power gating entry we write hbm pg entry request command and then we set pg register For power gating exit we clear pg register and wait for exit request hbm command. Exit power gating request might also be initiated by the firmware w/o explicit driver request The power gating state is tracked by pg_state member of me_hw Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 2 ++ drivers/misc/mei/hw-me.c | 92 +++++++++++++++++++++++++++++++++++++++++++++--- drivers/misc/mei/hw-me.h | 4 +++ drivers/misc/mei/hw.h | 1 + 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index a16b47c855aa..a725365e2150 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -732,12 +732,14 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) case MEI_PG_ISOLATION_ENTRY_RES_CMD: dev_dbg(&dev->pdev->dev, "power gate isolation entry response received\n"); + dev->pg_event = MEI_PG_EVENT_RECEIVED; if (waitqueue_active(&dev->wait_pg)) wake_up(&dev->wait_pg); break; case MEI_PG_ISOLATION_EXIT_REQ_CMD: dev_dbg(&dev->pdev->dev, "power gate isolation exit request received\n"); + dev->pg_event = MEI_PG_EVENT_RECEIVED; if (waitqueue_active(&dev->wait_pg)) wake_up(&dev->wait_pg); break; diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 02f3b0c3a2a3..4b048b67a248 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -109,9 +109,12 @@ static inline void mei_hcsr_set(struct mei_me_hw *hw, u32 hcsr) */ static void mei_me_hw_config(struct mei_device *dev) { + struct mei_me_hw *hw = to_me_hw(dev); u32 hcsr = mei_hcsr_read(to_me_hw(dev)); /* Doesn't change in runtime */ dev->hbuf_depth = (hcsr & H_CBD) >> 24; + + hw->pg_state = MEI_PG_OFF; } /** @@ -123,7 +126,8 @@ static void mei_me_hw_config(struct mei_device *dev) */ static inline enum mei_pg_state mei_me_pg_state(struct mei_device *dev) { - return MEI_PG_OFF; + struct mei_me_hw *hw = to_me_hw(dev); + return hw->pg_state; } /** @@ -472,6 +476,80 @@ static void mei_me_pg_exit(struct mei_device *dev) mei_me_reg_write(hw, H_HPG_CSR, reg); } +/** + * mei_me_pg_set_sync - perform pg entry procedure + * + * @dev: the device structure + * + * returns 0 on success an error code otherwise + */ +int mei_me_pg_set_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + + dev->pg_event = MEI_PG_EVENT_WAIT; + + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_ENTRY_REQ_CMD); + if (ret) + return ret; + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) { + mei_me_pg_enter(dev); + ret = 0; + } else { + ret = -ETIME; + } + + dev->pg_event = MEI_PG_EVENT_IDLE; + hw->pg_state = MEI_PG_ON; + + return ret; +} + +/** + * mei_me_pg_unset_sync - perform pg exit procedure + * + * @dev: the device structure + * + * returns 0 on success an error code otherwise + */ +int mei_me_pg_unset_sync(struct mei_device *dev) +{ + struct mei_me_hw *hw = to_me_hw(dev); + unsigned long timeout = mei_secs_to_jiffies(MEI_PGI_TIMEOUT); + int ret; + + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) + goto reply; + + dev->pg_event = MEI_PG_EVENT_WAIT; + + mei_me_pg_exit(dev); + + mutex_unlock(&dev->device_lock); + wait_event_timeout(dev->wait_pg, + dev->pg_event == MEI_PG_EVENT_RECEIVED, timeout); + mutex_lock(&dev->device_lock); + +reply: + if (dev->pg_event == MEI_PG_EVENT_RECEIVED) + ret = mei_hbm_pg(dev, MEI_PG_ISOLATION_EXIT_RES_CMD); + else + ret = -ETIME; + + dev->pg_event = MEI_PG_EVENT_IDLE; + hw->pg_state = MEI_PG_OFF; + + return ret; +} + /** * mei_me_pg_is_enabled - detect if PG is supported by HW * @@ -601,9 +679,15 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) dev->hbuf_is_ready = mei_hbuf_is_ready(dev); - rets = mei_irq_write_handler(dev, &complete_list); - - dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + /* + * During PG handshake only allowed write is the replay to the + * PG exit message, so block calling write function + * if the pg state is not idle + */ + if (dev->pg_event == MEI_PG_EVENT_IDLE) { + rets = mei_irq_write_handler(dev, &complete_list); + dev->hbuf_is_ready = mei_hbuf_is_ready(dev); + } mei_irq_compl_handler(dev, &complete_list); diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 893d5119fa9b..8b412820cc66 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -31,12 +31,16 @@ struct mei_me_hw { */ u32 host_hw_state; u32 me_hw_state; + enum mei_pg_state pg_state; }; #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) struct mei_device *mei_me_dev_init(struct pci_dev *pdev); +int mei_me_pg_set_sync(struct mei_device *dev); +int mei_me_pg_unset_sync(struct mei_device *dev); + irqreturn_t mei_me_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id); diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index ec4a91a534e6..9bad9cf7a8a0 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -31,6 +31,7 @@ #define MEI_IAMTHIF_STALL_TIMER 12 /* HPS */ #define MEI_IAMTHIF_READ_TIMER 10 /* HPS */ +#define MEI_PGI_TIMEOUT 1 /* PG Isolation time response 1 sec */ #define MEI_HBM_TIMEOUT 1 /* 1 second */ /* -- cgit v1.2.3 From a532bbedc85ff3b834ba81e49163a3f543be1775 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:52:01 +0200 Subject: mei: add function to check write queues The driver needs to check whether the write queue idle before entering power gating Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/init.c | 21 +++++++++++++++++++++ drivers/misc/mei/mei_dev.h | 2 ++ 2 files changed, 23 insertions(+) diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 4b1eb37344cb..abc5ea053bf7 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -303,7 +303,28 @@ void mei_stop(struct mei_device *dev) } EXPORT_SYMBOL_GPL(mei_stop); +/** + * mei_write_is_idle - check if the write queues are idle + * + * @dev: the device structure + * + * returns true of there is no pending write + */ +bool mei_write_is_idle(struct mei_device *dev) +{ + bool idle = (dev->dev_state == MEI_DEV_ENABLED && + list_empty(&dev->ctrl_wr_list.list) && + list_empty(&dev->write_list.list)); + dev_dbg(&dev->pdev->dev, "write pg: is idle[%d] state=%s ctrl=%d write=%d\n", + idle, + mei_dev_state_str(dev->dev_state), + list_empty(&dev->ctrl_wr_list.list), + list_empty(&dev->write_list.list)); + + return idle; +} +EXPORT_SYMBOL_GPL(mei_write_is_idle); void mei_device_init(struct mei_device *dev) { diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index bec6adef68bd..fe76e5b4cd2a 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -680,6 +680,8 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) bool mei_hbuf_acquire(struct mei_device *dev); +bool mei_write_is_idle(struct mei_device *dev); + #if IS_ENABLED(CONFIG_DEBUG_FS) int mei_dbgfs_register(struct mei_device *dev, const char *name); void mei_dbgfs_deregister(struct mei_device *dev); -- cgit v1.2.3 From 180ea05bcedbd67bb22a426bb8d831075727e34a Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:52:02 +0200 Subject: mei: me: add runtime pm framework Add runtime pm framework for ME devices. The runtime pm handlers are used to run me power gating isolation protocol Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 8 +++++ drivers/misc/mei/hw-me.h | 2 ++ drivers/misc/mei/pci-me.c | 92 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index a725365e2150..b9a4bb5921f1 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "mei_dev.h" #include "hbm.h" @@ -742,6 +743,13 @@ int mei_hbm_dispatch(struct mei_device *dev, struct mei_msg_hdr *hdr) dev->pg_event = MEI_PG_EVENT_RECEIVED; if (waitqueue_active(&dev->wait_pg)) wake_up(&dev->wait_pg); + else + /* + * If the driver is not waiting on this then + * this is HW initiated exit from PG. + * Start runtime pm resume sequence to exit from PG. + */ + pm_request_resume(&dev->pdev->dev); break; case HOST_CLIENT_PROPERTIES_RES_CMD: diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 8b412820cc66..0a75ab85b3a2 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -24,6 +24,8 @@ #include "mei_dev.h" #include "client.h" +#define MEI_ME_RPM_TIMEOUT 500 /* ms */ + struct mei_me_hw { void __iomem *mem_addr; /* diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 95889e2e31ff..fcbf270c2c2a 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -33,6 +33,8 @@ #include #include +#include + #include #include "mei_dev.h" @@ -212,6 +214,9 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto release_irq; } + pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + err = mei_register(dev); if (err) goto release_irq; @@ -220,6 +225,9 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) schedule_delayed_work(&dev->timer_work, HZ); + if (mei_pg_is_enabled(dev)) + pm_runtime_put_noidle(&pdev->dev); + dev_dbg(&pdev->dev, "initialization successful.\n"); return 0; @@ -259,6 +267,9 @@ static void mei_me_remove(struct pci_dev *pdev) if (!dev) return; + if (mei_pg_is_enabled(dev)) + pm_runtime_get_noresume(&pdev->dev); + hw = to_me_hw(dev); @@ -343,12 +354,89 @@ static int mei_me_pci_resume(struct device *device) return 0; } +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int mei_me_pm_runtime_idle(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + + dev_dbg(&pdev->dev, "rpm: me: runtime_idle\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + if (mei_write_is_idle(dev)) + pm_schedule_suspend(device, MEI_ME_RPM_TIMEOUT * 2); + + return -EBUSY; +} + +static int mei_me_pm_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: me: runtime suspend\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) + ret = mei_me_pg_set_sync(dev); + else + ret = -EAGAIN; + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: me: runtime suspend ret=%d\n", ret); + + return ret; +} + +static int mei_me_pm_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: me: runtime resume\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + ret = mei_me_pg_unset_sync(dev); + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: me: runtime resume ret = %d\n", ret); + + return ret; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops mei_me_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_me_pci_suspend, + mei_me_pci_resume) + SET_RUNTIME_PM_OPS( + mei_me_pm_runtime_suspend, + mei_me_pm_runtime_resume, + mei_me_pm_runtime_idle) +}; -static SIMPLE_DEV_PM_OPS(mei_me_pm_ops, mei_me_pci_suspend, mei_me_pci_resume); #define MEI_ME_PM_OPS (&mei_me_pm_ops) #else #define MEI_ME_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ /* * PCI driver structure */ -- cgit v1.2.3 From cfe5ab85634b5fdab2c90249517910ea3df63f6a Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 18 Mar 2014 22:52:03 +0200 Subject: mei: txe: add runtime pm framework Add runtime pm framework for TXE devices. The runtime pm handlers are used to run txe power gating isolation protocol. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-txe.h | 2 + drivers/misc/mei/pci-txe.c | 101 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 4 deletions(-) diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h index 799f9f25f424..e8dd2d165c25 100644 --- a/drivers/misc/mei/hw-txe.h +++ b/drivers/misc/mei/hw-txe.h @@ -22,6 +22,8 @@ #include "hw.h" #include "hw-txe-regs.h" +#define MEI_TXI_RPM_TIMEOUT 500 /* ms */ + /* Flatten Hierarchy interrupt cause */ #define TXE_INTR_READINESS_BIT 0 /* HISR_INT_0_STS */ #define TXE_INTR_READINESS HISR_INT_0_STS diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index ad3adb009a1e..31d86e76fb7f 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -137,12 +138,17 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto release_irq; } + pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); + pm_runtime_use_autosuspend(&pdev->dev); + err = mei_register(dev); if (err) goto release_irq; pci_set_drvdata(pdev, dev); + pm_runtime_put_noidle(&pdev->dev); + return 0; release_irq: @@ -187,6 +193,8 @@ static void mei_txe_remove(struct pci_dev *pdev) return; } + pm_runtime_get_noresume(&pdev->dev); + hw = to_txe_hw(dev); mei_stop(dev); @@ -265,15 +273,100 @@ static int mei_txe_pci_resume(struct device *device) return err; } +#endif /* CONFIG_PM_SLEEP */ + +#ifdef CONFIG_PM_RUNTIME +static int mei_txe_pm_runtime_idle(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + + dev_dbg(&pdev->dev, "rpm: txe: runtime_idle\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + if (mei_write_is_idle(dev)) + pm_schedule_suspend(device, MEI_TXI_RPM_TIMEOUT * 2); + + return -EBUSY; +} +static int mei_txe_pm_runtime_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: txe: runtime suspend\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; -static SIMPLE_DEV_PM_OPS(mei_txe_pm_ops, - mei_txe_pci_suspend, - mei_txe_pci_resume); + mutex_lock(&dev->device_lock); + + if (mei_write_is_idle(dev)) + ret = mei_txe_aliveness_set_sync(dev, 0); + else + ret = -EAGAIN; + + /* + * If everything is okay we're about to enter PCI low + * power state (D3) therefor we need to disable the + * interrupts towards host. + * However if device is not wakeable we do not enter + * D-low state and we need to keep the interrupt kicking + */ + if (!ret && pci_dev_run_wake(pdev)) + mei_disable_interrupts(dev); + + dev_dbg(&pdev->dev, "rpm: txe: runtime suspend ret=%d\n", ret); + + mutex_unlock(&dev->device_lock); + return ret; +} + +static int mei_txe_pm_runtime_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct mei_device *dev; + int ret; + + dev_dbg(&pdev->dev, "rpm: txe: runtime resume\n"); + + dev = pci_get_drvdata(pdev); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->device_lock); + + mei_enable_interrupts(dev); + + ret = mei_txe_aliveness_set_sync(dev, 1); + + mutex_unlock(&dev->device_lock); + + dev_dbg(&pdev->dev, "rpm: txe: runtime resume ret = %d\n", ret); + + return ret; +} +#endif /* CONFIG_PM_RUNTIME */ + +#ifdef CONFIG_PM +static const struct dev_pm_ops mei_txe_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mei_txe_pci_suspend, + mei_txe_pci_resume) + SET_RUNTIME_PM_OPS( + mei_txe_pm_runtime_suspend, + mei_txe_pm_runtime_resume, + mei_txe_pm_runtime_idle) +}; #define MEI_TXE_PM_OPS (&mei_txe_pm_ops) #else #define MEI_TXE_PM_OPS NULL -#endif /* CONFIG_PM_SLEEP */ +#endif /* CONFIG_PM */ + /* * PCI driver structure */ -- cgit v1.2.3 From 04bb139a071fef549892718f8965a7c61b1924e0 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:52:04 +0200 Subject: mei: use runtime pm in write and read flow Take rpm token on operation start to initiate rpm resume if needed. Mark last busy time, release token and advice rpm framework to try to autosuspend on operation end. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 84 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 13 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 8c078b808cd3..9273e89e6a15 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -415,6 +416,10 @@ void mei_host_client_init(struct work_struct *work) dev->reset_count = 0; mutex_unlock(&dev->device_lock); + + pm_runtime_mark_last_busy(&dev->pdev->dev); + dev_dbg(&dev->pdev->dev, "rpm: autosuspend\n"); + pm_runtime_autosuspend(&dev->pdev->dev); } /** @@ -425,6 +430,12 @@ void mei_host_client_init(struct work_struct *work) */ bool mei_hbuf_acquire(struct mei_device *dev) { + if (mei_pg_state(dev) == MEI_PG_ON || + dev->pg_event == MEI_PG_EVENT_WAIT) { + dev_dbg(&dev->pdev->dev, "device is in pg\n"); + return false; + } + if (!dev->hbuf_is_ready) { dev_dbg(&dev->pdev->dev, "hbuf is not ready\n"); return false; @@ -460,9 +471,18 @@ int mei_cl_disconnect(struct mei_cl *cl) if (cl->state != MEI_FILE_DISCONNECTING) return 0; + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; + if (!cb) { + rets = -ENOMEM; + goto free; + } cb->fop_type = MEI_FOP_CLOSE; if (mei_hbuf_acquire(dev)) { @@ -494,8 +514,7 @@ int mei_cl_disconnect(struct mei_cl *cl) cl_err(dev, cl, "wrong status client disconnect.\n"); if (err) - cl_dbg(dev, cl, "wait failed disconnect err=%08x\n", - err); + cl_dbg(dev, cl, "wait failed disconnect err=%d\n", err); cl_err(dev, cl, "failed to disconnect from FW client.\n"); } @@ -503,6 +522,10 @@ int mei_cl_disconnect(struct mei_cl *cl) mei_io_list_flush(&dev->ctrl_rd_list, cl); mei_io_list_flush(&dev->ctrl_wr_list, cl); free: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + mei_io_cb_free(cb); return rets; } @@ -557,6 +580,13 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) dev = cl->dev; + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, file); if (!cb) { rets = -ENOMEM; @@ -596,6 +626,10 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) rets = cl->status; out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + mei_io_cb_free(cb); return rets; } @@ -713,23 +747,32 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) return -ENOTTY; } + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } + cb = mei_io_cb_init(cl, NULL); - if (!cb) - return -ENOMEM; + if (!cb) { + rets = -ENOMEM; + goto out; + } /* always allocate at least client max message */ length = max_t(size_t, length, dev->me_clients[i].props.max_msg_length); rets = mei_io_cb_alloc_resp_buf(cb, length); if (rets) - goto err; + goto out; cb->fop_type = MEI_FOP_READ; if (mei_hbuf_acquire(dev)) { if (mei_hbm_cl_flow_control_req(dev, cl)) { - cl_err(dev, cl, "flow control send failed\n"); rets = -ENODEV; - goto err; + goto out; } + list_add_tail(&cb->list, &dev->read_list.list); } else { list_add_tail(&cb->list, &dev->ctrl_wr_list.list); @@ -737,9 +780,14 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) cl->read_cb = cb; - return rets; -err: - mei_io_cb_free(cb); +out: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + + if (rets) + mei_io_cb_free(cb); + return rets; } @@ -776,7 +824,7 @@ int mei_cl_irq_write(struct mei_cl *cl, struct mei_cl_cb *cb, return rets; if (rets == 0) { - cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); + cl_dbg(dev, cl, "No flow control credentials: not sending.\n"); return 0; } @@ -856,6 +904,12 @@ int mei_cl_write(struct mei_cl *cl, struct mei_cl_cb *cb, bool blocking) cl_dbg(dev, cl, "mei_cl_write %d\n", buf->size); + rets = pm_runtime_get(&dev->pdev->dev); + if (rets < 0 && rets != -EINPROGRESS) { + pm_runtime_put_noidle(&dev->pdev->dev); + cl_err(dev, cl, "rpm: get failed %d\n", rets); + return rets; + } cb->fop_type = MEI_FOP_WRITE; cb->buf_idx = 0; @@ -926,6 +980,10 @@ out: rets = buf->size; err: + cl_dbg(dev, cl, "rpm: autosuspend\n"); + pm_runtime_mark_last_busy(&dev->pdev->dev); + pm_runtime_put_autosuspend(&dev->pdev->dev); + return rets; } -- cgit v1.2.3 From e13fa90ce42d8e7ee501426ea414c8ae4a5366ef Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:52:05 +0200 Subject: mei: me: use runtime PG pm domain for non wakeable devices For non wakeable devices we can't use pci runtime framework as we are not able to wakeup from D3 states. Instead we create new pg runtime domain that only drives ME power gating protocol to reduce the power consumption. Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/mei_dev.h | 3 +++ drivers/misc/mei/pci-me.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index fe76e5b4cd2a..beff9ef319f8 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -419,6 +419,9 @@ struct mei_device { * Power Gating support */ enum mei_pg_event pg_event; +#ifdef CONFIG_PM_RUNTIME + struct dev_pm_domain pg_domain; +#endif /* CONFIG_PM_RUNTIME */ unsigned char rd_msg_buf[MEI_RD_MSG_BUF_SIZE]; /* control messages */ u32 rd_msg_hdr; diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index fcbf270c2c2a..88516b02c685 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -87,6 +87,14 @@ static const struct pci_device_id mei_me_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, mei_me_pci_tbl); +#ifdef CONFIG_PM_RUNTIME +static inline void mei_me_set_pm_domain(struct mei_device *dev); +static inline void mei_me_unset_pm_domain(struct mei_device *dev); +#else +static inline void mei_me_set_pm_domain(struct mei_device *dev) {} +static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} +#endif /* CONFIG_PM_RUNTIME */ + /** * mei_quirk_probe - probe for devices that doesn't valid ME interface * @@ -225,6 +233,14 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) schedule_delayed_work(&dev->timer_work, HZ); + /* + * For not wake-able HW runtime pm framework + * can't be used on pci device level. + * Use domain runtime pm callbacks instead. + */ + if (!pci_dev_run_wake(pdev)) + mei_me_set_pm_domain(dev); + if (mei_pg_is_enabled(dev)) pm_runtime_put_noidle(&pdev->dev); @@ -276,6 +292,9 @@ static void mei_me_remove(struct pci_dev *pdev) dev_dbg(&pdev->dev, "stop\n"); mei_stop(dev); + if (!pci_dev_run_wake(pdev)) + mei_me_unset_pm_domain(dev); + /* disable interrupts */ mei_disable_interrupts(dev); @@ -421,6 +440,37 @@ static int mei_me_pm_runtime_resume(struct device *device) return ret; } + +/** + * mei_me_set_pm_domain - fill and set pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_me_set_pm_domain(struct mei_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + + if (pdev->dev.bus && pdev->dev.bus->pm) { + dev->pg_domain.ops = *pdev->dev.bus->pm; + + dev->pg_domain.ops.runtime_suspend = mei_me_pm_runtime_suspend; + dev->pg_domain.ops.runtime_resume = mei_me_pm_runtime_resume; + dev->pg_domain.ops.runtime_idle = mei_me_pm_runtime_idle; + + pdev->dev.pm_domain = &dev->pg_domain; + } +} + +/** + * mei_me_unset_pm_domain - clean pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_me_unset_pm_domain(struct mei_device *dev) +{ + /* stop using pm callbacks if any */ + dev->pdev->dev.pm_domain = NULL; +} #endif /* CONFIG_PM_RUNTIME */ #ifdef CONFIG_PM -- cgit v1.2.3 From d2d56faebaed1dd9bc011fcceed7df6b1bea8fac Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 18 Mar 2014 22:52:06 +0200 Subject: mei: txe: use runtime PG pm domain for non wakeable devices For non wakeable devices we can't use pci runtime framework as we are not able to wakeup from D3 states. Instead we create new pg runtime domain that only drives TXE power gating protocol to reduce the power consumption. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/pci-txe.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 31d86e76fb7f..2c3f5625a04e 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -41,6 +41,13 @@ static const struct pci_device_id mei_txe_pci_tbl[] = { }; MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl); +#ifdef CONFIG_PM_RUNTIME +static inline void mei_txe_set_pm_domain(struct mei_device *dev); +static inline void mei_txe_unset_pm_domain(struct mei_device *dev); +#else +static inline void mei_txe_set_pm_domain(struct mei_device *dev) {} +static inline void mei_txe_unset_pm_domain(struct mei_device *dev) {} +#endif /* CONFIG_PM_RUNTIME */ static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) { @@ -147,6 +154,14 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pci_set_drvdata(pdev, dev); + /* + * For not wake-able HW runtime pm framework + * can't be used on pci device level. + * Use domain runtime pm callbacks instead. + */ + if (!pci_dev_run_wake(pdev)) + mei_txe_set_pm_domain(dev); + pm_runtime_put_noidle(&pdev->dev); return 0; @@ -199,6 +214,9 @@ static void mei_txe_remove(struct pci_dev *pdev) mei_stop(dev); + if (!pci_dev_run_wake(pdev)) + mei_txe_unset_pm_domain(dev); + /* disable interrupts */ mei_disable_interrupts(dev); free_irq(pdev->irq, dev); @@ -350,6 +368,37 @@ static int mei_txe_pm_runtime_resume(struct device *device) return ret; } + +/** + * mei_txe_set_pm_domain - fill and set pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_txe_set_pm_domain(struct mei_device *dev) +{ + struct pci_dev *pdev = dev->pdev; + + if (pdev->dev.bus && pdev->dev.bus->pm) { + dev->pg_domain.ops = *pdev->dev.bus->pm; + + dev->pg_domain.ops.runtime_suspend = mei_txe_pm_runtime_suspend; + dev->pg_domain.ops.runtime_resume = mei_txe_pm_runtime_resume; + dev->pg_domain.ops.runtime_idle = mei_txe_pm_runtime_idle; + + pdev->dev.pm_domain = &dev->pg_domain; + } +} + +/** + * mei_txe_unset_pm_domain - clean pm domian stucture for device + * + * @dev: mei_device + */ +static inline void mei_txe_unset_pm_domain(struct mei_device *dev) +{ + /* stop using pm callbacks if any */ + dev->pdev->dev.pm_domain = NULL; +} #endif /* CONFIG_PM_RUNTIME */ #ifdef CONFIG_PM -- cgit v1.2.3 From 61a1aea7c7cb40de071e202cfaa31fa2c1fca8ba Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 18 Mar 2014 22:52:07 +0200 Subject: mei: me: bump hbm version to 1.1 to support power gating Communicate hbm version 1.1 to firmware to tell that we now support power gating isolation protocol Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/mei/hw.h b/drivers/misc/mei/hw.h index 9bad9cf7a8a0..dd448e58cc87 100644 --- a/drivers/misc/mei/hw.h +++ b/drivers/misc/mei/hw.h @@ -37,7 +37,7 @@ /* * MEI Version */ -#define HBM_MINOR_VERSION 0 +#define HBM_MINOR_VERSION 1 #define HBM_MAJOR_VERSION 1 /* -- cgit v1.2.3 From 04dd36619564c3fcf590c2bf2619b14c09cd0749 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 31 Mar 2014 17:59:23 +0300 Subject: mei: extract fw status registers Fetch FW status registers, as they are important in in understanding of FW reset reasons Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 55 ++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mei/hw-txe-regs.h | 2 +- drivers/misc/mei/hw-txe.c | 37 +++++++++++++++++++++++++++- drivers/misc/mei/init.c | 10 +++++--- drivers/misc/mei/mei_dev.h | 28 +++++++++++++++++++++ 5 files changed, 127 insertions(+), 5 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 4b048b67a248..a31bc4c36cca 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -696,10 +696,65 @@ end: mutex_unlock(&dev->device_lock); return IRQ_HANDLED; } + +/** + * mei_me_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns 0 on success an error code otherwise + */ +static int mei_me_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + const u32 pci_cfg_reg[] = {PCI_CFG_HFS_1, PCI_CFG_HFS_2}; + int i; + + if (!fw_status) + return -EINVAL; + + switch (dev->pdev->device) { + case MEI_DEV_ID_IBXPK_1: + case MEI_DEV_ID_IBXPK_2: + case MEI_DEV_ID_CPT_1: + case MEI_DEV_ID_PBG_1: + case MEI_DEV_ID_PPT_1: + case MEI_DEV_ID_PPT_2: + case MEI_DEV_ID_PPT_3: + case MEI_DEV_ID_LPT_H: + case MEI_DEV_ID_LPT_W: + case MEI_DEV_ID_LPT_LP: + case MEI_DEV_ID_LPT_HR: + case MEI_DEV_ID_WPT_LP: + fw_status->count = 2; + break; + case MEI_DEV_ID_ICH10_1: + case MEI_DEV_ID_ICH10_2: + case MEI_DEV_ID_ICH10_3: + case MEI_DEV_ID_ICH10_4: + fw_status->count = 1; + break; + default: + fw_status->count = 0; + break; + } + + for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + pci_cfg_reg[i], &fw_status->status[i]); + if (ret) + return ret; + } + return 0; +} + static const struct mei_hw_ops mei_me_hw_ops = { .pg_state = mei_me_pg_state, + .fw_status = mei_me_fw_status, .host_is_ready = mei_me_host_is_ready, .hw_is_ready = mei_me_hw_is_ready, diff --git a/drivers/misc/mei/hw-txe-regs.h b/drivers/misc/mei/hw-txe-regs.h index 7283c24c1af1..f19229c4e655 100644 --- a/drivers/misc/mei/hw-txe-regs.h +++ b/drivers/misc/mei/hw-txe-regs.h @@ -89,7 +89,7 @@ enum { # define PCI_CFG_TXE_FW_STS0_ERR_CODE_MSK 0x0000F000 # define PCI_CFG_TXE_FW_STS0_OP_MODE_MSK 0x000F0000 # define PCI_CFG_TXE_FW_STS0_RST_CNT_MSK 0x00F00000 - +#define PCI_CFG_TXE_FW_STS1 0x48 #define IPC_BASE_ADDR 0x80400 /* SeC IPC Base Address */ diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 4a6f567f4f80..455a6a56b82b 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -620,7 +620,10 @@ static int mei_txe_write(struct mei_device *dev, mei_txe_input_ready_interrupt_enable(dev); if (!mei_txe_is_input_ready(dev)) { - dev_err(&dev->pdev->dev, "Input is not ready"); + struct mei_fw_status fw_status; + mei_fw_status(dev, &fw_status); + dev_err(&dev->pdev->dev, "Input is not ready " FW_STS_FMT "\n", + FW_STS_PRM(fw_status)); return -EAGAIN; } @@ -1039,8 +1042,40 @@ end: return IRQ_HANDLED; } + +/** + * mei_txe_fw_status - retrieve fw status from the pci config space + * + * @dev: the device structure + * @fw_status: fw status registers storage + * + * returns: 0 on success an error code otherwise + */ +static int mei_txe_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + const u32 pci_cfg_reg[] = {PCI_CFG_TXE_FW_STS0, PCI_CFG_TXE_FW_STS1}; + int i; + + if (!fw_status) + return -EINVAL; + + fw_status->count = 2; + + for (i = 0; i < fw_status->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + pci_cfg_reg[i], &fw_status->status[i]); + if (ret) + return ret; + } + + return 0; +} + static const struct mei_hw_ops mei_txe_hw_ops = { + .fw_status = mei_txe_fw_status, .host_is_ready = mei_txe_host_is_ready, .pg_state = mei_txe_pg_state, diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index abc5ea053bf7..b3f70eb17934 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -74,9 +74,13 @@ int mei_reset(struct mei_device *dev) if (state != MEI_DEV_INITIALIZING && state != MEI_DEV_DISABLED && state != MEI_DEV_POWER_DOWN && - state != MEI_DEV_POWER_UP) - dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", - mei_dev_state_str(state)); + state != MEI_DEV_POWER_UP) { + struct mei_fw_status fw_status; + mei_fw_status(dev, &fw_status); + dev_warn(&dev->pdev->dev, + "unexpected reset: dev_state = %s " FW_STS_FMT "\n", + mei_dev_state_str(state), FW_STS_PRM(fw_status)); + } /* we're already in reset, cancel the init timer * if the reset was called due the hbm protocol error diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index beff9ef319f8..1aaaf0b6681c 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -153,6 +153,20 @@ struct mei_msg_data { unsigned char *data; }; +/* Maximum number of processed FW status registers */ +#define MEI_FW_STATUS_MAX 2 + +/* + * struct mei_fw_status - storage of FW status data + * + * @count - number of actually available elements in array + * @status - FW status registers + */ +struct mei_fw_status { + int count; + u32 status[MEI_FW_STATUS_MAX]; +}; + /** * struct mei_me_client - representation of me (fw) client * @@ -213,6 +227,7 @@ struct mei_cl { /** struct mei_hw_ops * + * @fw_status - read FW status from PCI config space * @host_is_ready - query for host readiness * @hw_is_ready - query if hw is ready @@ -240,6 +255,8 @@ struct mei_cl { */ struct mei_hw_ops { + int (*fw_status)(struct mei_device *dev, + struct mei_fw_status *fw_status); bool (*host_is_ready)(struct mei_device *dev); bool (*hw_is_ready)(struct mei_device *dev); @@ -681,6 +698,17 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) return dev->ops->rdbuf_full_slots(dev); } +static inline int mei_fw_status(struct mei_device *dev, + struct mei_fw_status *fw_status) +{ + return dev->ops->fw_status(dev, fw_status); +} + +#define FW_STS_FMT "%08X %08X" +#define FW_STS_PRM(fw_status) \ + (fw_status).count > 0 ? (fw_status).status[0] : 0xDEADBEEF, \ + (fw_status).count > 1 ? (fw_status).status[1] : 0xDEADBEEF + bool mei_hbuf_acquire(struct mei_device *dev); bool mei_write_is_idle(struct mei_device *dev); -- cgit v1.2.3 From 86113500c060bccb0f08bdcadcecc0bd267fd25a Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 31 Mar 2014 17:59:24 +0300 Subject: mei: make return values consistent across the driver Follow-up for bits missed in commit 7ca96aa278f8b9983184e318b06a0ed9ad0297b8 mei: make return values consistent across the driver Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/bus.c | 2 +- drivers/misc/mei/client.c | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index ddc5ac92a200..d5b9ed4452ef 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -247,7 +247,7 @@ static int ___mei_cl_send(struct mei_cl *cl, u8 *buf, size_t length, return id; if (length > dev->me_clients[id].props.max_msg_length) - return -EINVAL; + return -EFBIG; cb = mei_io_cb_init(cl, NULL); if (!cb) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 9273e89e6a15..3349f626aa88 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -768,10 +768,9 @@ int mei_cl_read_start(struct mei_cl *cl, size_t length) cb->fop_type = MEI_FOP_READ; if (mei_hbuf_acquire(dev)) { - if (mei_hbm_cl_flow_control_req(dev, cl)) { - rets = -ENODEV; + rets = mei_hbm_cl_flow_control_req(dev, cl); + if (rets < 0) goto out; - } list_add_tail(&cb->list, &dev->read_list.list); } else { -- cgit v1.2.3 From e4d8270e604c3202131bac607969605ac397b893 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sun, 27 Apr 2014 15:42:21 +0300 Subject: mei: set connecting state just upon connection request is sent to the fw Adding power gating introduced new waiting state for client also during connection attempt, a connection request can be queued for later either due device is power gated or due to other on going connection. We setting client connection state before start of full connect procedure so in both cased the client state will be MEI_FILE_CONNECTING which create interlock between the two connection attempts, both detecting that another connection is in progress. The interlock is resolved by moving client to connecting state only upon connection request transmission, so the first cb in queue can be processed. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/amthif.c | 2 -- drivers/misc/mei/bus.c | 2 -- drivers/misc/mei/client.c | 1 + drivers/misc/mei/main.c | 1 - drivers/misc/mei/wd.c | 2 -- 5 files changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/misc/mei/amthif.c b/drivers/misc/mei/amthif.c index b8deb3455480..0d6234db00fa 100644 --- a/drivers/misc/mei/amthif.c +++ b/drivers/misc/mei/amthif.c @@ -111,8 +111,6 @@ int mei_amthif_host_init(struct mei_device *dev) return ret; } - cl->state = MEI_FILE_CONNECTING; - ret = mei_cl_connect(cl, NULL); dev->iamthif_state = MEI_IAMTHIF_IDLE; diff --git a/drivers/misc/mei/bus.c b/drivers/misc/mei/bus.c index d5b9ed4452ef..0e993ef28b94 100644 --- a/drivers/misc/mei/bus.c +++ b/drivers/misc/mei/bus.c @@ -427,8 +427,6 @@ int mei_cl_enable_device(struct mei_cl_device *device) mutex_lock(&dev->device_lock); - cl->state = MEI_FILE_CONNECTING; - err = mei_cl_connect(cl, NULL); if (err < 0) { mutex_unlock(&dev->device_lock); diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 3349f626aa88..59d20c599b16 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -597,6 +597,7 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) /* run hbuf acquire last so we don't have to undo */ if (!mei_cl_is_other_connecting(cl) && mei_hbuf_acquire(dev)) { + cl->state = MEI_FILE_CONNECTING; if (mei_hbm_cl_connect_req(dev, cl)) { rets = -ENODEV; goto out; diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 147413145c97..66f0a1a06451 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -467,7 +467,6 @@ static int mei_ioctl_connect_client(struct file *file, } cl->me_client_id = dev->me_clients[i].client_id; - cl->state = MEI_FILE_CONNECTING; dev_dbg(&dev->pdev->dev, "Connect to FW Client ID = %d\n", cl->me_client_id); diff --git a/drivers/misc/mei/wd.c b/drivers/misc/mei/wd.c index ebf1cbc198fd..a84a664dfccb 100644 --- a/drivers/misc/mei/wd.c +++ b/drivers/misc/mei/wd.c @@ -84,8 +84,6 @@ int mei_wd_host_init(struct mei_device *dev) return ret; } - cl->state = MEI_FILE_CONNECTING; - ret = mei_cl_connect(cl, NULL); if (ret) { -- cgit v1.2.3 From d3ba720dd58cdf6630fee4b89482c465d5ad0d0f Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Tue, 8 Apr 2014 18:45:53 -0700 Subject: Drivers: hv: Eliminate the channel spinlock in the callback path By ensuring that we set the callback handler to NULL in the channel close path on the same CPU that the channel is bound to, we can eliminate this lock acquisition and release in a performance critical path. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 16 ++++++++++++---- drivers/hv/channel_mgmt.c | 11 +++++++---- drivers/hv/connection.c | 11 ++++------- include/linux/hyperv.h | 2 ++ 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 602ca86a6488..740edec161bb 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -471,18 +471,26 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle) } EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl); +static void reset_channel_cb(void *arg) +{ + struct vmbus_channel *channel = arg; + + channel->onchannel_callback = NULL; +} + static void vmbus_close_internal(struct vmbus_channel *channel) { struct vmbus_channel_close_channel *msg; int ret; - unsigned long flags; channel->state = CHANNEL_OPEN_STATE; channel->sc_creation_callback = NULL; /* Stop callback and cancel the timer asap */ - spin_lock_irqsave(&channel->inbound_lock, flags); - channel->onchannel_callback = NULL; - spin_unlock_irqrestore(&channel->inbound_lock, flags); + if (channel->target_cpu != smp_processor_id()) + smp_call_function_single(channel->target_cpu, reset_channel_cb, + channel, true); + else + reset_channel_cb(channel); /* Send a closing message */ diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index fa920469bf10..6f7fdd9a7e77 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -365,7 +365,7 @@ static u32 next_vp; * performance critical channels (IDE, SCSI and Network) will be uniformly * distributed across all available CPUs. */ -static u32 get_vp_index(uuid_le *type_guid) +static void init_vp_index(struct vmbus_channel *channel, uuid_le *type_guid) { u32 cur_cpu; int i; @@ -387,10 +387,13 @@ static u32 get_vp_index(uuid_le *type_guid) * Also if the channel is not a performance critical * channel, bind it to cpu 0. */ - return 0; + channel->target_cpu = 0; + channel->target_vp = 0; + return; } cur_cpu = (++next_vp % max_cpus); - return hv_context.vp_index[cur_cpu]; + channel->target_cpu = cur_cpu; + channel->target_vp = hv_context.vp_index[cur_cpu]; } /* @@ -438,7 +441,7 @@ static void vmbus_onoffer(struct vmbus_channel_message_header *hdr) offer->connection_id; } - newchannel->target_vp = get_vp_index(&offer->offer.if_type); + init_vp_index(newchannel, &offer->offer.if_type); memcpy(&newchannel->offermsg, offer, sizeof(struct vmbus_channel_offer_channel)); diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 2e7801af466e..df2363ea017f 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -277,7 +277,6 @@ struct vmbus_channel *relid2channel(u32 relid) static void process_chn_event(u32 relid) { struct vmbus_channel *channel; - unsigned long flags; void *arg; bool read_state; u32 bytes_to_read; @@ -296,13 +295,12 @@ static void process_chn_event(u32 relid) /* * A channel once created is persistent even when there * is no driver handling the device. An unloading driver - * sets the onchannel_callback to NULL under the - * protection of the channel inbound_lock. Thus, checking - * and invoking the driver specific callback takes care of - * orderly unloading of the driver. + * sets the onchannel_callback to NULL on the same CPU + * as where this interrupt is handled (in an interrupt context). + * Thus, checking and invoking the driver specific callback takes + * care of orderly unloading of the driver. */ - spin_lock_irqsave(&channel->inbound_lock, flags); if (channel->onchannel_callback != NULL) { arg = channel->channel_callback_context; read_state = channel->batched_reading; @@ -327,7 +325,6 @@ static void process_chn_event(u32 relid) pr_err("no channel callback for relid - %u\n", relid); } - spin_unlock_irqrestore(&channel->inbound_lock, flags); } /* diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 2d7b4f139c32..a274e089df78 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -696,6 +696,8 @@ struct vmbus_channel { * preserve the earlier behavior. */ u32 target_vp; + /* The corresponding CPUID in the guest */ + u32 target_cpu; /* * Support for sub-channels. For high performance devices, * it will be useful to have multiple sub-channels to support -- cgit v1.2.3 From 3a28fa35d6658703cd26f9c16aaea0eae06afd40 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Tue, 8 Apr 2014 18:45:54 -0700 Subject: Drivers: hv: vmbus: Implement per-CPU mapping of relid to channel Currently the mapping of the relID to channel is done under the protection of a single spin lock. Starting with ws2012, each channel is bound to a specific VCPU in the guest. Use this binding to eliminate the spin lock by setting up per-cpu state for mapping relId to the channel. Signed-off-by: K. Y. Srinivasan Reviewed-by: Haiyang Zhang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel_mgmt.c | 41 ++++++++++++++++++++++++++++++++++++++++- drivers/hv/connection.c | 24 +++++++++++++++++++++++- drivers/hv/hv.c | 2 ++ drivers/hv/hyperv_vmbus.h | 5 +++++ include/linux/hyperv.h | 5 +++++ 5 files changed, 75 insertions(+), 2 deletions(-) diff --git a/drivers/hv/channel_mgmt.c b/drivers/hv/channel_mgmt.c index 6f7fdd9a7e77..6c8b032cacba 100644 --- a/drivers/hv/channel_mgmt.c +++ b/drivers/hv/channel_mgmt.c @@ -149,6 +149,7 @@ static struct vmbus_channel *alloc_channel(void) spin_lock_init(&channel->sc_lock); INIT_LIST_HEAD(&channel->sc_list); + INIT_LIST_HEAD(&channel->percpu_list); channel->controlwq = create_workqueue("hv_vmbus_ctl"); if (!channel->controlwq) { @@ -188,7 +189,20 @@ static void free_channel(struct vmbus_channel *channel) queue_work(vmbus_connection.work_queue, &channel->work); } +static void percpu_channel_enq(void *arg) +{ + struct vmbus_channel *channel = arg; + int cpu = smp_processor_id(); + + list_add_tail(&channel->percpu_list, &hv_context.percpu_list[cpu]); +} +static void percpu_channel_deq(void *arg) +{ + struct vmbus_channel *channel = arg; + + list_del(&channel->percpu_list); +} /* * vmbus_process_rescind_offer - @@ -210,6 +224,12 @@ static void vmbus_process_rescind_offer(struct work_struct *work) msg.header.msgtype = CHANNELMSG_RELID_RELEASED; vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released)); + if (channel->target_cpu != smp_processor_id()) + smp_call_function_single(channel->target_cpu, + percpu_channel_deq, channel, true); + else + percpu_channel_deq(channel); + if (channel->primary_channel == NULL) { spin_lock_irqsave(&vmbus_connection.channel_lock, flags); list_del(&channel->listentry); @@ -245,6 +265,7 @@ static void vmbus_process_offer(struct work_struct *work) work); struct vmbus_channel *channel; bool fnew = true; + bool enq = false; int ret; unsigned long flags; @@ -264,12 +285,22 @@ static void vmbus_process_offer(struct work_struct *work) } } - if (fnew) + if (fnew) { list_add_tail(&newchannel->listentry, &vmbus_connection.chn_list); + enq = true; + } spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags); + if (enq) { + if (newchannel->target_cpu != smp_processor_id()) + smp_call_function_single(newchannel->target_cpu, + percpu_channel_enq, + newchannel, true); + else + percpu_channel_enq(newchannel); + } if (!fnew) { /* * Check to see if this is a sub-channel. @@ -282,6 +313,14 @@ static void vmbus_process_offer(struct work_struct *work) spin_lock_irqsave(&channel->sc_lock, flags); list_add_tail(&newchannel->sc_list, &channel->sc_list); spin_unlock_irqrestore(&channel->sc_lock, flags); + + if (newchannel->target_cpu != smp_processor_id()) + smp_call_function_single(newchannel->target_cpu, + percpu_channel_enq, + newchannel, true); + else + percpu_channel_enq(newchannel); + newchannel->state = CHANNEL_OPEN_STATE; if (channel->sc_creation_callback != NULL) channel->sc_creation_callback(newchannel); diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index df2363ea017f..7f10c151632a 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -234,6 +234,28 @@ cleanup: return ret; } +/* + * Map the given relid to the corresponding channel based on the + * per-cpu list of channels that have been affinitized to this CPU. + * This will be used in the channel callback path as we can do this + * mapping in a lock-free fashion. + */ +static struct vmbus_channel *pcpu_relid2channel(u32 relid) +{ + struct vmbus_channel *channel; + struct vmbus_channel *found_channel = NULL; + int cpu = smp_processor_id(); + struct list_head *pcpu_head = &hv_context.percpu_list[cpu]; + + list_for_each_entry(channel, pcpu_head, percpu_list) { + if (channel->offermsg.child_relid == relid) { + found_channel = channel; + break; + } + } + + return found_channel; +} /* * relid2channel - Get the channel object given its @@ -285,7 +307,7 @@ static void process_chn_event(u32 relid) * Find the channel based on this relid and invokes the * channel callback to process the event */ - channel = relid2channel(relid); + channel = pcpu_relid2channel(relid); if (!channel) { pr_err("channel not found for relid - %u\n", relid); diff --git a/drivers/hv/hv.c b/drivers/hv/hv.c index bcb49502c3bf..edfc8488cb03 100644 --- a/drivers/hv/hv.c +++ b/drivers/hv/hv.c @@ -383,6 +383,8 @@ void hv_synic_init(void *arg) */ rdmsrl(HV_X64_MSR_VP_INDEX, vp_index); hv_context.vp_index[cpu] = (u32)vp_index; + + INIT_LIST_HEAD(&hv_context.percpu_list[cpu]); return; } diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h index 860134da8039..18d1a8404cbc 100644 --- a/drivers/hv/hyperv_vmbus.h +++ b/drivers/hv/hyperv_vmbus.h @@ -510,6 +510,11 @@ struct hv_context { * basis. */ struct tasklet_struct *event_dpc[NR_CPUS]; + /* + * To optimize the mapping of relid to channel, maintain + * per-cpu list of the channels based on their CPU affinity. + */ + struct list_head percpu_list[NR_CPUS]; }; extern struct hv_context hv_context; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index a274e089df78..08cfaff8a072 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -734,6 +734,11 @@ struct vmbus_channel { * Support per-channel state for use by vmbus drivers. */ void *per_channel_state; + /* + * To support per-cpu lookup mapping of relid to channel, + * link up channels based on their CPU affinity. + */ + struct list_head percpu_list; }; static inline void set_channel_read_state(struct vmbus_channel *c, bool state) -- cgit v1.2.3 From 24b8a406bf5c3675bd193069cc6ab84a66fcbfd7 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Fri, 25 Apr 2014 17:03:50 +0200 Subject: hv: Remove unnecessary comparison of unsigned against 0 pfncount is of type u32 and thus can never be smaller than 0. Found by the coverity scanner, CID 143213. Signed-off-by: Tobias Klauser Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 740edec161bb..284cf66489f4 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -682,8 +682,7 @@ int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, u32 pfncount = NUM_PAGES_SPANNED(multi_pagebuffer->offset, multi_pagebuffer->len); - - if ((pfncount < 0) || (pfncount > MAX_MULTIPAGE_BUFFER_COUNT)) + if (pfncount > MAX_MULTIPAGE_BUFFER_COUNT) return -EINVAL; /* -- cgit v1.2.3 From ae339336dc950b9b05e7ccd3565dd3e8781c06d9 Mon Sep 17 00:00:00 2001 From: "K. Y. Srinivasan" Date: Wed, 23 Apr 2014 13:53:39 -0700 Subject: Drivers: hv: balloon: Ensure pressure reports are posted regularly The current code posts periodic memory pressure status from a dedicated thread. Under some conditions, especially when we are releasing a lot of memory into the guest, we may not send timely pressure reports back to the host. Fix this issue by reporting pressure in all contexts that can be active in this driver. Signed-off-by: K. Y. Srinivasan Cc: stable@vger.kernel.org Signed-off-by: Greg Kroah-Hartman --- drivers/hv/hv_balloon.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/hv/hv_balloon.c b/drivers/hv/hv_balloon.c index 7e6d78dc9437..5e90c5d771a7 100644 --- a/drivers/hv/hv_balloon.c +++ b/drivers/hv/hv_balloon.c @@ -19,6 +19,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -459,6 +460,11 @@ static bool do_hot_add; */ static uint pressure_report_delay = 45; +/* + * The last time we posted a pressure report to host. + */ +static unsigned long last_post_time; + module_param(hot_add, bool, (S_IRUGO | S_IWUSR)); MODULE_PARM_DESC(hot_add, "If set attempt memory hot_add"); @@ -542,6 +548,7 @@ struct hv_dynmem_device { static struct hv_dynmem_device dm_device; +static void post_status(struct hv_dynmem_device *dm); #ifdef CONFIG_MEMORY_HOTPLUG static void hv_bring_pgs_online(unsigned long start_pfn, unsigned long size) @@ -612,7 +619,7 @@ static void hv_mem_hot_add(unsigned long start, unsigned long size, * have not been "onlined" within the allowed time. */ wait_for_completion_timeout(&dm_device.ol_waitevent, 5*HZ); - + post_status(&dm_device); } return; @@ -951,11 +958,17 @@ static void post_status(struct hv_dynmem_device *dm) { struct dm_status status; struct sysinfo val; + unsigned long now = jiffies; + unsigned long last_post = last_post_time; if (pressure_report_delay > 0) { --pressure_report_delay; return; } + + if (!time_after(now, (last_post_time + HZ))) + return; + si_meminfo(&val); memset(&status, 0, sizeof(struct dm_status)); status.hdr.type = DM_STATUS_REPORT; @@ -983,6 +996,14 @@ static void post_status(struct hv_dynmem_device *dm) if (status.hdr.trans_id != atomic_read(&trans_id)) return; + /* + * If the last post time that we sampled has changed, + * we have raced, don't post the status. + */ + if (last_post != last_post_time) + return; + + last_post_time = jiffies; vmbus_sendpacket(dm->dev->channel, &status, sizeof(struct dm_status), (unsigned long)NULL, @@ -1117,7 +1138,7 @@ static void balloon_up(struct work_struct *dummy) if (ret == -EAGAIN) msleep(20); - + post_status(&dm_device); } while (ret == -EAGAIN); if (ret) { @@ -1144,8 +1165,10 @@ static void balloon_down(struct hv_dynmem_device *dm, struct dm_unballoon_response resp; int i; - for (i = 0; i < range_count; i++) + for (i = 0; i < range_count; i++) { free_balloon_pages(dm, &range_array[i]); + post_status(&dm_device); + } if (req->more_pages == 1) return; -- cgit v1.2.3 From fc1a5dbe49d42d9d46800e2af4cc75ee01c3d141 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 23 Apr 2014 15:18:06 +0200 Subject: misc: Add hardware dependencies to Atmel drivers According to the driver descriptions, the atmel_pwm and atmel-ssc drivers are only useful on Atmel AT32 and AT91 systems. So add hardware dependencies to these drivers to hide them on other systems. Signed-off-by: Jean Delvare Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 8baff0effc7d..fe2230cb64e2 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -53,7 +53,7 @@ config AD525X_DPOT_SPI config ATMEL_PWM tristate "Atmel AT32/AT91 PWM support" - depends on HAVE_CLK + depends on HAVE_CLK && (AVR32 || ARCH_AT91 || COMPILE_TEST) help This option enables device driver support for the PWM channels on certain Atmel processors. Pulse Width Modulation is used for @@ -200,7 +200,7 @@ config ICS932S401 config ATMEL_SSC tristate "Device driver for Atmel SSC peripheral" - depends on HAS_IOMEM + depends on HAS_IOMEM && (AVR32 || ARCH_AT91 || COMPILE_TEST) ---help--- This option enables device driver support for Atmel Synchronized Serial Communication peripheral (SSC). -- cgit v1.2.3 From 32d9dbe37965cb2e7fce47ea836a57cab4ffe5d4 Mon Sep 17 00:00:00 2001 From: Masanari Iida Date: Thu, 24 Apr 2014 12:25:49 +0900 Subject: misc: genwqe: Fix format string mismatch in card_debugfs.c Fix two format string mismatch in genwqe_init_debugfs() Signed-off-by: Masanari Iida Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_debugfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/genwqe/card_debugfs.c b/drivers/misc/genwqe/card_debugfs.c index 50d2096ea1c7..0a33ade64109 100644 --- a/drivers/misc/genwqe/card_debugfs.c +++ b/drivers/misc/genwqe/card_debugfs.c @@ -348,7 +348,7 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) char name[64]; unsigned int i; - sprintf(card_name, "%s%u_card", GENWQE_DEVNAME, cd->card_idx); + sprintf(card_name, "%s%d_card", GENWQE_DEVNAME, cd->card_idx); root = debugfs_create_dir(card_name, cd->debugfs_genwqe); if (!root) { @@ -454,7 +454,7 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) } for (i = 0; i < GENWQE_MAX_VFS; i++) { - sprintf(name, "vf%d_jobtimeout_msec", i); + sprintf(name, "vf%u_jobtimeout_msec", i); file = debugfs_create_u32(name, 0666, root, &cd->vf_jobtimeout_msec[i]); -- cgit v1.2.3 From 403007457dce735ca4fa27a1df4c9af305e53652 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdelin Date: Tue, 1 Apr 2014 11:04:28 -0400 Subject: misc: (ds1682) replace obsolete simple_strtoull() with kstrtoull() simple_strtoull() is obsolete, use the newer kstrtoull() instead. Signed-off-by: Sebastien Bourdelin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ds1682.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/misc/ds1682.c b/drivers/misc/ds1682.c index 6a672f9ef522..b909fb30232a 100644 --- a/drivers/misc/ds1682.c +++ b/drivers/misc/ds1682.c @@ -85,7 +85,6 @@ static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, { struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); struct i2c_client *client = to_i2c_client(dev); - char *endp; u64 val; __le32 val_le; int rc; @@ -93,8 +92,8 @@ static ssize_t ds1682_store(struct device *dev, struct device_attribute *attr, dev_dbg(dev, "ds1682_store() called on %s\n", attr->attr.name); /* Decode input */ - val = simple_strtoull(buf, &endp, 0); - if (buf == endp) { + rc = kstrtoull(buf, 0, &val); + if (rc < 0) { dev_dbg(dev, "input string not a number\n"); return -EINVAL; } -- cgit v1.2.3 From a48839509ad4be29e64e245e752c529c125ede49 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Fri, 28 Mar 2014 16:20:28 -0500 Subject: dt/bindings: add binding for ARM Versatile character LCD Add binding doc for Versatile platforms character LCD controller interface. Signed-off-by: Rob Herring Cc: Pawel Moll Cc: Mark Rutland Cc: Ian Campbell Cc: Kumar Gala Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/misc/arm-charlcd.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Documentation/devicetree/bindings/misc/arm-charlcd.txt diff --git a/Documentation/devicetree/bindings/misc/arm-charlcd.txt b/Documentation/devicetree/bindings/misc/arm-charlcd.txt new file mode 100644 index 000000000000..e28e2aac47f1 --- /dev/null +++ b/Documentation/devicetree/bindings/misc/arm-charlcd.txt @@ -0,0 +1,18 @@ +ARM Versatile Character LCD +----------------------------------------------------- +This binding defines the character LCD interface found on ARM Versatile AB +and PB reference platforms. + +Required properties: +- compatible : "arm,versatile-clcd" +- reg : Location and size of character LCD registers + +Optional properties: +- interrupts - single interrupt for character LCD. The character LCD can + operate in polled mode without an interrupt. + +Example: + lcd@10008000 { + compatible = "arm,versatile-lcd"; + reg = <0x10008000 0x1000>; + }; -- cgit v1.2.3 From 4f0638100fe305837d583aed75c51c53047a2524 Mon Sep 17 00:00:00 2001 From: Rob Herring Date: Fri, 28 Mar 2014 16:20:29 -0500 Subject: misc: arm-charlcd: add DT probe support Add the DT match table to enable DT based probe matching. Signed-off-by: Rob Herring Cc: Arnd Bergmann Cc: Greg Kroah-Hartman Signed-off-by: Greg Kroah-Hartman --- drivers/misc/arm-charlcd.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/misc/arm-charlcd.c b/drivers/misc/arm-charlcd.c index b7ebf8021d99..c72e96b523ed 100644 --- a/drivers/misc/arm-charlcd.c +++ b/drivers/misc/arm-charlcd.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -366,11 +367,17 @@ static const struct dev_pm_ops charlcd_pm_ops = { .resume = charlcd_resume, }; +static const struct of_device_id charlcd_match[] = { + { .compatible = "arm,versatile-lcd", }, + {} +}; + static struct platform_driver charlcd_driver = { .driver = { .name = DRIVERNAME, .owner = THIS_MODULE, .pm = &charlcd_pm_ops, + .of_match_table = of_match_ptr(charlcd_match), }, .remove = __exit_p(charlcd_remove), }; -- cgit v1.2.3 From fd1a73c60ee0bfd3fefc96ad270effd69b350aba Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 16 May 2014 11:16:54 +0200 Subject: misc: pch_phub: Fix Kconfig dependencies The pch_phub driver is for a companion chip to the Intel Atom E600 series processors. These are 32-bit x86 processors so the driver is only needed on X86_32. Add COMPILE_TEST as an alternative, so that the driver can still be build-tested elsewhere. Signed-off-by: Jean Delvare Cc: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index fe2230cb64e2..1e6df18e8218 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -468,7 +468,7 @@ config BMP085_SPI config PCH_PHUB tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB" select GENERIC_NET_UTILS - depends on PCI + depends on PCI && (X86_32 || COMPILE_TEST) help This driver is for PCH(Platform controller Hub) PHUB(Packet Hub) of Intel Topcliff which is an IOH(Input/Output Hub) for x86 embedded -- cgit v1.2.3 From 34470e0bfae223e3f22bd2bd6e0e1dac366c9290 Mon Sep 17 00:00:00 2001 From: David Fries Date: Tue, 8 Apr 2014 22:37:08 -0500 Subject: connector: allow multiple messages to be sent in one packet This increases the amount of bundling to reduce the number of packets sent. For the one wire use there can be multiple struct w1_netlink_cmd in a struct w1_netlink_msg and multiple of those in struct cn_msg, and with this change multiple of those in a struct nlmsghdr, and at each level the len identifies there being multiple of the next. Signed-off-by: David Fries Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- Documentation/connector/connector.txt | 13 ++++++++++--- drivers/connector/connector.c | 17 +++++++++++++++-- include/linux/connector.h | 1 + 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Documentation/connector/connector.txt b/Documentation/connector/connector.txt index e5c5f5e6ab70..e56abdb21975 100644 --- a/Documentation/connector/connector.txt +++ b/Documentation/connector/connector.txt @@ -24,7 +24,8 @@ netlink based networking for inter-process communication in a significantly easier way: int cn_add_callback(struct cb_id *id, char *name, void (*callback) (struct cn_msg *, struct netlink_skb_parms *)); -void cn_netlink_send(struct cn_msg *msg, u32 __group, int gfp_mask); +void cn_netlink_send_multi(struct cn_msg *msg, u16 len, u32 portid, u32 __group, int gfp_mask); +void cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, int gfp_mask); struct cb_id { @@ -71,15 +72,21 @@ void cn_del_callback(struct cb_id *id); struct cb_id *id - unique connector's user identifier. -int cn_netlink_send(struct cn_msg *msg, u32 __groups, int gfp_mask); +int cn_netlink_send_multi(struct cn_msg *msg, u16 len, u32 portid, u32 __groups, int gfp_mask); +int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __groups, int gfp_mask); Sends message to the specified groups. It can be safely called from softirq context, but may silently fail under strong memory pressure. If there are no listeners for given group -ESRCH can be returned. struct cn_msg * - message header(with attached data). + u16 len - for *_multi multiple cn_msg messages can be sent + u32 port - destination port. + If non-zero the message will be sent to the + given port, which should be set to the + original sender. u32 __group - destination group. - If __group is zero, then appropriate group will + If port and __group is zero, then appropriate group will be searched through all registered connector users, and message will be delivered to the group which was created for user with the same ID as in msg. diff --git a/drivers/connector/connector.c b/drivers/connector/connector.c index b14f1d36f897..f612d68629dc 100644 --- a/drivers/connector/connector.c +++ b/drivers/connector/connector.c @@ -43,6 +43,8 @@ static struct cn_dev cdev; static int cn_already_initialized; /* + * Sends mult (multiple) cn_msg at a time. + * * msg->seq and msg->ack are used to determine message genealogy. * When someone sends message it puts there locally unique sequence * and random acknowledge numbers. Sequence number may be copied into @@ -62,10 +64,13 @@ static int cn_already_initialized; * the acknowledgement number in the original message + 1, then it is * a new message. * + * If msg->len != len, then additional cn_msg messages are expected following + * the first msg. + * * The message is sent to, the portid if given, the group if given, both if * both, or if both are zero then the group is looked up and sent there. */ -int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, +int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, gfp_t gfp_mask) { struct cn_callback_entry *__cbq; @@ -98,7 +103,7 @@ int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, if (!portid && !netlink_has_listeners(dev->nls, group)) return -ESRCH; - size = sizeof(*msg) + msg->len; + size = sizeof(*msg) + len; skb = nlmsg_new(size, gfp_mask); if (!skb) @@ -121,6 +126,14 @@ int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, gfp_mask); return netlink_unicast(dev->nls, skb, portid, !(gfp_mask&__GFP_WAIT)); } +EXPORT_SYMBOL_GPL(cn_netlink_send_mult); + +/* same as cn_netlink_send_mult except msg->len is used for len */ +int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, + gfp_t gfp_mask) +{ + return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask); +} EXPORT_SYMBOL_GPL(cn_netlink_send); /* diff --git a/include/linux/connector.h b/include/linux/connector.h index be9c4747d511..f8fe8637d771 100644 --- a/include/linux/connector.h +++ b/include/linux/connector.h @@ -71,6 +71,7 @@ struct cn_dev { int cn_add_callback(struct cb_id *id, const char *name, void (*callback)(struct cn_msg *, struct netlink_skb_parms *)); void cn_del_callback(struct cb_id *); +int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 group, gfp_t gfp_mask); int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 group, gfp_t gfp_mask); int cn_queue_add_callback(struct cn_queue_dev *dev, const char *name, -- cgit v1.2.3 From 8a0427d192e6043834414210dd14cc1289daff18 Mon Sep 17 00:00:00 2001 From: David Fries Date: Tue, 8 Apr 2014 22:37:09 -0500 Subject: w1: optional bundling of netlink kernel replies Applications can submit a set of commands in one packet to the kernel, and in some cases it is required such as reading the temperature sensor results. This adds an option W1_CN_BUNDLE to the flags of cn_msg to request the kernel to reply in one packet for efficiency. The cn_msg flags now check for unknown flag values and return an error if one is seen. See "Proper handling of unknown flags in system calls" http://lwn.net/Articles/588444/ This corrects the ack values returned as per the protocol standard, namely the original ack for status messages and seq + 1 for all others such as the data returned from a read. Some of the common variable names have been standardized as follows. struct cn_msg *cn struct w1_netlink_msg *msg struct w1_netlink_cmd *cmd struct w1_master *dev When an argument and a function scope variable would collide, add req_ to the argument. Signed-off-by: David Fries Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- Documentation/connector/connector.txt | 2 +- Documentation/w1/w1.generic | 2 +- Documentation/w1/w1.netlink | 13 +- drivers/w1/w1.h | 8 - drivers/w1/w1_netlink.c | 649 +++++++++++++++++++++------------- drivers/w1/w1_netlink.h | 36 ++ 6 files changed, 447 insertions(+), 263 deletions(-) diff --git a/Documentation/connector/connector.txt b/Documentation/connector/connector.txt index e56abdb21975..f6215f95149b 100644 --- a/Documentation/connector/connector.txt +++ b/Documentation/connector/connector.txt @@ -118,7 +118,7 @@ acknowledge number MUST be the same + 1. If we receive a message and its sequence number is not equal to one we are expecting, then it is a new message. If we receive a message and its sequence number is the same as one we are expecting, but its -acknowledge is not equal to the acknowledge number in the original +acknowledge is not equal to the sequence number in the original message + 1, then it is a new message. Obviously, the protocol header contains the above id. diff --git a/Documentation/w1/w1.generic b/Documentation/w1/w1.generic index a31c5a242973..b2033c64c7da 100644 --- a/Documentation/w1/w1.generic +++ b/Documentation/w1/w1.generic @@ -82,7 +82,7 @@ driver - (standard) symlink to the w1 driver w1_master_add - Manually register a slave device w1_master_attempts - the number of times a search was attempted w1_master_max_slave_count - - the maximum slaves that may be attached to a master + - maximum number of slaves to search for at a time w1_master_name - the name of the device (w1_bus_masterX) w1_master_pullup - 5V strong pullup 0 enabled, 1 disabled w1_master_remove - Manually remove a slave device diff --git a/Documentation/w1/w1.netlink b/Documentation/w1/w1.netlink index 927a52cc0519..ef2727192d69 100644 --- a/Documentation/w1/w1.netlink +++ b/Documentation/w1/w1.netlink @@ -30,7 +30,7 @@ Protocol. W1_SLAVE_CMD userspace command for slave device (read/write/touch) - __u8 res - reserved + __u8 status - error indication from kernel __u16 len - size of data attached to this header data union { __u8 id[8]; - slave unique device id @@ -44,10 +44,14 @@ Protocol. __u8 cmd - command opcode. W1_CMD_READ - read command W1_CMD_WRITE - write command - W1_CMD_TOUCH - touch command - (write and sample data back to userspace) W1_CMD_SEARCH - search command W1_CMD_ALARM_SEARCH - alarm search command + W1_CMD_TOUCH - touch command + (write and sample data back to userspace) + W1_CMD_RESET - send bus reset + W1_CMD_SLAVE_ADD - add slave to kernel list + W1_CMD_SLAVE_REMOVE - remove slave from kernel list + W1_CMD_LIST_SLAVES - get slaves list from kernel __u8 res - reserved __u16 len - length of data for this command For read command data must be allocated like for write command @@ -87,8 +91,7 @@ format: id0 ... idN Each message is at most 4k in size, so if number of master devices - exceeds this, it will be split into several messages, - cn.seq will be increased for each one. + exceeds this, it will be split into several messages. W1 search and alarm search commands. request: diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h index 734dab7fc687..56a49ba41d83 100644 --- a/drivers/w1/w1.h +++ b/drivers/w1/w1.h @@ -203,7 +203,6 @@ enum w1_master_flags { * @search_id: allows continuing a search * @refcnt: reference count * @priv: private data storage - * @priv_size: size allocated * @enable_pullup: allows a strong pullup * @pullup_duration: time for the next strong pullup * @flags: one of w1_master_flags @@ -214,7 +213,6 @@ enum w1_master_flags { * @dev: sysfs device * @bus_master: io operations available * @seq: sequence number used for netlink broadcasts - * @portid: destination for the current netlink command */ struct w1_master { @@ -241,7 +239,6 @@ struct w1_master atomic_t refcnt; void *priv; - int priv_size; /** 5V strong pullup enabled flag, 1 enabled, zero disabled. */ int enable_pullup; @@ -260,11 +257,6 @@ struct w1_master struct w1_bus_master *bus_master; u32 seq; - /* port id to send netlink responses to. The value is temporarily - * stored here while processing a message, set after locking the - * mutex, zero before unlocking the mutex. - */ - u32 portid; }; /** diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index a02704a59321..351a2978ba72 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -29,51 +29,247 @@ #include "w1_netlink.h" #if defined(CONFIG_W1_CON) && (defined(CONFIG_CONNECTOR) || (defined(CONFIG_CONNECTOR_MODULE) && defined(CONFIG_W1_MODULE))) -void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) + +/* Bundle together everything required to process a request in one memory + * allocation. + */ +struct w1_cb_block { + atomic_t refcnt; + u32 portid; /* Sending process port ID */ + /* maximum value for first_cn->len */ + u16 maxlen; + /* pointers to building up the reply message */ + struct cn_msg *first_cn; /* fixed once the structure is populated */ + struct cn_msg *cn; /* advances as cn_msg is appeneded */ + struct w1_netlink_msg *msg; /* advances as w1_netlink_msg is appened */ + struct w1_netlink_cmd *cmd; /* advances as cmds are appened */ + struct w1_netlink_msg *cur_msg; /* currently message being processed */ + /* copy of the original request follows */ + struct cn_msg request_cn; + /* followed by variable length: + * cn_msg, data (w1_netlink_msg and w1_netlink_cmd) + * one or more struct w1_cb_node + * reply first_cn, data (w1_netlink_msg and w1_netlink_cmd) + */ +}; +struct w1_cb_node { + struct w1_async_cmd async; + /* pointers within w1_cb_block and cn data */ + struct w1_cb_block *block; + struct w1_netlink_msg *msg; + struct w1_slave *sl; + struct w1_master *dev; +}; + +/** + * w1_reply_len() - calculate current reply length, compare to maxlen + * @block: block to calculate + * + * Calculates the current message length including possible multiple + * cn_msg and data, excludes the first sizeof(struct cn_msg). Direclty + * compariable to maxlen and usable to send the message. + */ +static u16 w1_reply_len(struct w1_cb_block *block) +{ + if (!block->cn) + return 0; + return (u8 *)block->cn - (u8 *)block->first_cn + block->cn->len; +} + +static void w1_unref_block(struct w1_cb_block *block) +{ + if (atomic_sub_return(1, &block->refcnt) == 0) { + u16 len = w1_reply_len(block); + if (len) { + cn_netlink_send_mult(block->first_cn, len, + block->portid, 0, GFP_KERNEL); + } + kfree(block); + } +} + +/** + * w1_reply_make_space() - send message if needed to make space + * @block: block to make space on + * @space: how many bytes requested + * + * Verify there is enough room left for the caller to add "space" bytes to the + * message, if there isn't send the message and reset. + */ +static void w1_reply_make_space(struct w1_cb_block *block, u16 space) +{ + u16 len = w1_reply_len(block); + if (len + space >= block->maxlen) { + cn_netlink_send_mult(block->first_cn, len, block->portid, 0, GFP_KERNEL); + block->first_cn->len = 0; + block->cn = NULL; + block->msg = NULL; + block->cmd = NULL; + } +} + +/* Early send when replies aren't bundled. */ +static void w1_netlink_check_send(struct w1_cb_block *block) +{ + if (!(block->request_cn.flags & W1_CN_BUNDLE) && block->cn) + w1_reply_make_space(block, block->maxlen); +} + +/** + * w1_netlink_setup_msg() - prepare to write block->msg + * @block: block to operate on + * @ack: determines if cn can be reused + * + * block->cn will be setup with the correct ack, advancing if needed + * block->cn->len does not include space for block->msg + * block->msg advances but remains uninitialized + */ +static void w1_netlink_setup_msg(struct w1_cb_block *block, u32 ack) +{ + if (block->cn && block->cn->ack == ack) { + block->msg = (struct w1_netlink_msg *)(block->cn->data + block->cn->len); + } else { + /* advance or set to data */ + if (block->cn) + block->cn = (struct cn_msg *)(block->cn->data + + block->cn->len); + else + block->cn = block->first_cn; + + memcpy(block->cn, &block->request_cn, sizeof(*block->cn)); + block->cn->len = 0; + block->cn->ack = ack; + block->msg = (struct w1_netlink_msg *)block->cn->data; + } +} + +/* Append cmd to msg, include cmd->data as well. This is because + * any following data goes with the command and in the case of a read is + * the results. + */ +static void w1_netlink_queue_cmd(struct w1_cb_block *block, + struct w1_netlink_cmd *cmd) +{ + u32 space; + w1_reply_make_space(block, sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + sizeof(*cmd) + cmd->len); + + /* There's a status message sent after each command, so no point + * in trying to bundle this cmd after an existing one, because + * there won't be one. Allocate and copy over a new cn_msg. + */ + w1_netlink_setup_msg(block, block->request_cn.seq + 1); + memcpy(block->msg, block->cur_msg, sizeof(*block->msg)); + block->cn->len += sizeof(*block->msg); + block->msg->len = 0; + block->cmd = (struct w1_netlink_cmd *)(block->msg->data); + + space = sizeof(*cmd) + cmd->len; + if (block->cmd != cmd) + memcpy(block->cmd, cmd, space); + block->cn->len += space; + block->msg->len += space; +} + +/* Append req_msg and req_cmd, no other commands and no data from req_cmd are + * copied. + */ +static void w1_netlink_queue_status(struct w1_cb_block *block, + struct w1_netlink_msg *req_msg, struct w1_netlink_cmd *req_cmd, + int error) { - char buf[sizeof(struct cn_msg) + sizeof(struct w1_netlink_msg)]; - struct cn_msg *m = (struct cn_msg *)buf; - struct w1_netlink_msg *w = (struct w1_netlink_msg *)(m+1); + u16 space = sizeof(struct cn_msg) + sizeof(*req_msg) + sizeof(*req_cmd); + w1_reply_make_space(block, space); + w1_netlink_setup_msg(block, block->request_cn.ack); + + memcpy(block->msg, req_msg, sizeof(*req_msg)); + block->cn->len += sizeof(*req_msg); + block->msg->len = 0; + block->msg->status = (u8)-error; + if (req_cmd) { + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)block->msg->data; + memcpy(cmd, req_cmd, sizeof(*cmd)); + block->cn->len += sizeof(*cmd); + block->msg->len += sizeof(*cmd); + cmd->len = 0; + } + w1_netlink_check_send(block); +} - memset(buf, 0, sizeof(buf)); +/** + * w1_netlink_send_error() - sends the error message now + * @cn: original cn_msg + * @msg: original w1_netlink_msg + * @portid: where to send it + * @error: error status + * + * Use when a block isn't available to queue the message to and cn, msg + * might not be contiguous. + */ +static void w1_netlink_send_error(struct cn_msg *cn, struct w1_netlink_msg *msg, + int portid, int error) +{ + struct { + struct cn_msg cn; + struct w1_netlink_msg msg; + } packet; + memcpy(&packet.cn, cn, sizeof(packet.cn)); + memcpy(&packet.msg, msg, sizeof(packet.msg)); + packet.cn.len = sizeof(packet.msg); + packet.msg.len = 0; + packet.msg.status = (u8)-error; + cn_netlink_send(&packet.cn, portid, 0, GFP_KERNEL); +} + +/** + * w1_netlink_send() - sends w1 netlink notifications + * @dev: w1_master the even is associated with or for + * @msg: w1_netlink_msg message to be sent + * + * This are notifications generated from the kernel. + */ +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) +{ + struct { + struct cn_msg cn; + struct w1_netlink_msg msg; + } packet; + memset(&packet, 0, sizeof(packet)); - m->id.idx = CN_W1_IDX; - m->id.val = CN_W1_VAL; + packet.cn.id.idx = CN_W1_IDX; + packet.cn.id.val = CN_W1_VAL; - m->seq = dev->seq++; - m->len = sizeof(struct w1_netlink_msg); + packet.cn.seq = dev->seq++; + packet.cn.len = sizeof(*msg); - memcpy(w, msg, sizeof(struct w1_netlink_msg)); + memcpy(&packet.msg, msg, sizeof(*msg)); + packet.msg.len = 0; - cn_netlink_send(m, dev->portid, 0, GFP_KERNEL); + cn_netlink_send(&packet.cn, 0, 0, GFP_KERNEL); } static void w1_send_slave(struct w1_master *dev, u64 rn) { - struct cn_msg *msg = dev->priv; - struct w1_netlink_msg *hdr = (struct w1_netlink_msg *)(msg + 1); - struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)(hdr + 1); - int avail; + struct w1_cb_block *block = dev->priv; + struct w1_netlink_cmd *cache_cmd = block->cmd; u64 *data; - avail = dev->priv_size - cmd->len; + w1_reply_make_space(block, sizeof(*data)); - if (avail < 8) { - msg->ack++; - cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL); - - msg->len = sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd); - hdr->len = sizeof(struct w1_netlink_cmd); - cmd->len = 0; + /* Add cmd back if the packet was sent */ + if (!block->cmd) { + cache_cmd->len = 0; + w1_netlink_queue_cmd(block, cache_cmd); } - data = (void *)(cmd + 1) + cmd->len; + data = (u64 *)(block->cmd->data + block->cmd->len); *data = rn; - cmd->len += 8; - hdr->len += 8; - msg->len += 8; + block->cn->len += sizeof(*data); + block->msg->len += sizeof(*data); + block->cmd->len += sizeof(*data); } static void w1_found_send_slave(struct w1_master *dev, u64 rn) @@ -85,40 +281,15 @@ static void w1_found_send_slave(struct w1_master *dev, u64 rn) } /* Get the current slave list, or search (with or without alarm) */ -static int w1_get_slaves(struct w1_master *dev, - struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr, - struct w1_netlink_cmd *req_cmd) +static int w1_get_slaves(struct w1_master *dev, struct w1_netlink_cmd *req_cmd) { - struct cn_msg *msg; - struct w1_netlink_msg *hdr; - struct w1_netlink_cmd *cmd; struct w1_slave *sl; - msg = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!msg) - return -ENOMEM; - - msg->id = req_msg->id; - msg->seq = req_msg->seq; - msg->ack = 0; - msg->len = sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd); - - hdr = (struct w1_netlink_msg *)(msg + 1); - cmd = (struct w1_netlink_cmd *)(hdr + 1); - - hdr->type = W1_MASTER_CMD; - hdr->id = req_hdr->id; - hdr->len = sizeof(struct w1_netlink_cmd); - - cmd->cmd = req_cmd->cmd; - cmd->len = 0; - - dev->priv = msg; - dev->priv_size = PAGE_SIZE - msg->len - sizeof(struct cn_msg); + req_cmd->len = 0; + w1_netlink_queue_cmd(dev->priv, req_cmd); if (req_cmd->cmd == W1_CMD_LIST_SLAVES) { - __u64 rn; + u64 rn; mutex_lock(&dev->list_mutex); list_for_each_entry(sl, &dev->slist, w1_slave_entry) { memcpy(&rn, &sl->reg_num, sizeof(rn)); @@ -126,73 +297,26 @@ static int w1_get_slaves(struct w1_master *dev, } mutex_unlock(&dev->list_mutex); } else { - w1_search_process_cb(dev, cmd->cmd == W1_CMD_ALARM_SEARCH ? + w1_search_process_cb(dev, req_cmd->cmd == W1_CMD_ALARM_SEARCH ? W1_ALARM_SEARCH : W1_SEARCH, w1_found_send_slave); } - msg->ack = 0; - cn_netlink_send(msg, dev->portid, 0, GFP_KERNEL); - - dev->priv = NULL; - dev->priv_size = 0; - - kfree(msg); - return 0; } -static int w1_send_read_reply(struct cn_msg *msg, struct w1_netlink_msg *hdr, - struct w1_netlink_cmd *cmd, u32 portid) -{ - void *data; - struct w1_netlink_msg *h; - struct w1_netlink_cmd *c; - struct cn_msg *cm; - int err; - - data = kzalloc(sizeof(struct cn_msg) + - sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd) + - cmd->len, GFP_KERNEL); - if (!data) - return -ENOMEM; - - cm = (struct cn_msg *)(data); - h = (struct w1_netlink_msg *)(cm + 1); - c = (struct w1_netlink_cmd *)(h + 1); - - memcpy(cm, msg, sizeof(struct cn_msg)); - memcpy(h, hdr, sizeof(struct w1_netlink_msg)); - memcpy(c, cmd, sizeof(struct w1_netlink_cmd)); - - cm->ack = msg->seq+1; - cm->len = sizeof(struct w1_netlink_msg) + - sizeof(struct w1_netlink_cmd) + cmd->len; - - h->len = sizeof(struct w1_netlink_cmd) + cmd->len; - - memcpy(c->data, cmd->data, c->len); - - err = cn_netlink_send(cm, portid, 0, GFP_KERNEL); - - kfree(data); - - return err; -} - -static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, - struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) +static int w1_process_command_io(struct w1_master *dev, + struct w1_netlink_cmd *cmd) { int err = 0; switch (cmd->cmd) { case W1_CMD_TOUCH: w1_touch_block(dev, cmd->data, cmd->len); - w1_send_read_reply(msg, hdr, cmd, dev->portid); + w1_netlink_queue_cmd(dev->priv, cmd); break; case W1_CMD_READ: w1_read_block(dev, cmd->data, cmd->len); - w1_send_read_reply(msg, hdr, cmd, dev->portid); + w1_netlink_queue_cmd(dev->priv, cmd); break; case W1_CMD_WRITE: w1_write_block(dev, cmd->data, cmd->len); @@ -206,14 +330,13 @@ static int w1_process_command_io(struct w1_master *dev, struct cn_msg *msg, } static int w1_process_command_addremove(struct w1_master *dev, - struct cn_msg *msg, struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) { struct w1_slave *sl; int err = 0; struct w1_reg_num *id; - if (cmd->len != 8) + if (cmd->len != sizeof(*id)) return -EINVAL; id = (struct w1_reg_num *)cmd->data; @@ -241,7 +364,6 @@ static int w1_process_command_addremove(struct w1_master *dev, } static int w1_process_command_master(struct w1_master *dev, - struct cn_msg *req_msg, struct w1_netlink_msg *req_hdr, struct w1_netlink_cmd *req_cmd) { int err = -EINVAL; @@ -254,13 +376,13 @@ static int w1_process_command_master(struct w1_master *dev, case W1_CMD_ALARM_SEARCH: case W1_CMD_LIST_SLAVES: mutex_unlock(&dev->bus_mutex); - err = w1_get_slaves(dev, req_msg, req_hdr, req_cmd); + err = w1_get_slaves(dev, req_cmd); mutex_lock(&dev->bus_mutex); break; case W1_CMD_READ: case W1_CMD_WRITE: case W1_CMD_TOUCH: - err = w1_process_command_io(dev, req_msg, req_hdr, req_cmd); + err = w1_process_command_io(dev, req_cmd); break; case W1_CMD_RESET: err = w1_reset_bus(dev); @@ -269,8 +391,7 @@ static int w1_process_command_master(struct w1_master *dev, case W1_CMD_SLAVE_REMOVE: mutex_unlock(&dev->bus_mutex); mutex_lock(&dev->mutex); - err = w1_process_command_addremove(dev, req_msg, req_hdr, - req_cmd); + err = w1_process_command_addremove(dev, req_cmd); mutex_unlock(&dev->mutex); mutex_lock(&dev->bus_mutex); break; @@ -282,22 +403,21 @@ static int w1_process_command_master(struct w1_master *dev, return err; } -static int w1_process_command_slave(struct w1_slave *sl, struct cn_msg *msg, - struct w1_netlink_msg *hdr, struct w1_netlink_cmd *cmd) +static int w1_process_command_slave(struct w1_slave *sl, + struct w1_netlink_cmd *cmd) { dev_dbg(&sl->master->dev, "%s: %02x.%012llx.%02x: cmd=%02x, len=%u.\n", __func__, sl->reg_num.family, (unsigned long long)sl->reg_num.id, sl->reg_num.crc, cmd->cmd, cmd->len); - return w1_process_command_io(sl->master, msg, hdr, cmd); + return w1_process_command_io(sl->master, cmd); } -static int w1_process_command_root(struct cn_msg *msg, - struct w1_netlink_msg *mcmd, u32 portid) +static int w1_process_command_root(struct cn_msg *req_cn, u32 portid) { - struct w1_master *m; + struct w1_master *dev; struct cn_msg *cn; - struct w1_netlink_msg *w; + struct w1_netlink_msg *msg; u32 *id; cn = kmalloc(PAGE_SIZE, GFP_KERNEL); @@ -307,32 +427,30 @@ static int w1_process_command_root(struct cn_msg *msg, cn->id.idx = CN_W1_IDX; cn->id.val = CN_W1_VAL; - cn->seq = msg->seq; - cn->ack = 1; + cn->seq = req_cn->seq; + cn->ack = req_cn->seq + 1; cn->len = sizeof(struct w1_netlink_msg); - w = (struct w1_netlink_msg *)(cn + 1); + msg = (struct w1_netlink_msg *)cn->data; - w->type = W1_LIST_MASTERS; - w->status = 0; - w->len = 0; - id = (u32 *)(w + 1); + msg->type = W1_LIST_MASTERS; + msg->status = 0; + msg->len = 0; + id = (u32 *)msg->data; mutex_lock(&w1_mlock); - list_for_each_entry(m, &w1_masters, w1_master_entry) { + list_for_each_entry(dev, &w1_masters, w1_master_entry) { if (cn->len + sizeof(*id) > PAGE_SIZE - sizeof(struct cn_msg)) { cn_netlink_send(cn, portid, 0, GFP_KERNEL); - cn->ack++; cn->len = sizeof(struct w1_netlink_msg); - w->len = 0; - id = (u32 *)(w + 1); + msg->len = 0; + id = (u32 *)msg->data; } - *id = m->id; - w->len += sizeof(*id); + *id = dev->id; + msg->len += sizeof(*id); cn->len += sizeof(*id); id++; } - cn->ack = 0; cn_netlink_send(cn, portid, 0, GFP_KERNEL); mutex_unlock(&w1_mlock); @@ -340,100 +458,44 @@ static int w1_process_command_root(struct cn_msg *msg, return 0; } -static int w1_netlink_send_error(struct cn_msg *rcmsg, struct w1_netlink_msg *rmsg, - struct w1_netlink_cmd *rcmd, int portid, int error) -{ - struct cn_msg *cmsg; - struct w1_netlink_msg *msg; - struct w1_netlink_cmd *cmd; - - cmsg = kzalloc(sizeof(*msg) + sizeof(*cmd) + sizeof(*cmsg), GFP_KERNEL); - if (!cmsg) - return -ENOMEM; - - msg = (struct w1_netlink_msg *)(cmsg + 1); - cmd = (struct w1_netlink_cmd *)(msg + 1); - - memcpy(cmsg, rcmsg, sizeof(*cmsg)); - cmsg->len = sizeof(*msg); - - memcpy(msg, rmsg, sizeof(*msg)); - msg->len = 0; - msg->status = (short)-error; - - if (rcmd) { - memcpy(cmd, rcmd, sizeof(*cmd)); - cmd->len = 0; - msg->len += sizeof(*cmd); - cmsg->len += sizeof(*cmd); - } - - error = cn_netlink_send(cmsg, portid, 0, GFP_KERNEL); - kfree(cmsg); - - return error; -} - -/* Bundle together a reference count, the full message, and broken out - * commands to be executed on each w1 master kthread in one memory allocation. - */ -struct w1_cb_block { - atomic_t refcnt; - u32 portid; /* Sending process port ID */ - struct cn_msg msg; - /* cn_msg data */ - /* one or more variable length struct w1_cb_node */ -}; -struct w1_cb_node { - struct w1_async_cmd async; - /* pointers within w1_cb_block and msg data */ - struct w1_cb_block *block; - struct w1_netlink_msg *m; - struct w1_slave *sl; - struct w1_master *dev; -}; - static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) { struct w1_cb_node *node = container_of(async_cmd, struct w1_cb_node, async); - u16 mlen = node->m->len; - u8 *cmd_data = node->m->data; + u16 mlen = node->msg->len; + u16 len; int err = 0; struct w1_slave *sl = node->sl; - struct w1_netlink_cmd *cmd = NULL; + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)node->msg->data; mutex_lock(&dev->bus_mutex); - dev->portid = node->block->portid; + dev->priv = node->block; if (sl && w1_reset_select_slave(sl)) err = -ENODEV; + node->block->cur_msg = node->msg; while (mlen && !err) { - cmd = (struct w1_netlink_cmd *)cmd_data; - if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) { err = -E2BIG; break; } if (sl) - err = w1_process_command_slave(sl, &node->block->msg, - node->m, cmd); + err = w1_process_command_slave(sl, cmd); else - err = w1_process_command_master(dev, &node->block->msg, - node->m, cmd); + err = w1_process_command_master(dev, cmd); + w1_netlink_check_send(node->block); - w1_netlink_send_error(&node->block->msg, node->m, cmd, - node->block->portid, err); + w1_netlink_queue_status(node->block, node->msg, cmd, err); err = 0; - cmd_data += cmd->len + sizeof(struct w1_netlink_cmd); - mlen -= cmd->len + sizeof(struct w1_netlink_cmd); + len = sizeof(*cmd) + cmd->len; + cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); + mlen -= len; } if (!cmd || err) - w1_netlink_send_error(&node->block->msg, node->m, cmd, - node->block->portid, err); + w1_netlink_queue_status(node->block, node->msg, cmd, err); /* ref taken in w1_search_slave or w1_search_master_id when building * the block @@ -442,99 +504,186 @@ static void w1_process_cb(struct w1_master *dev, struct w1_async_cmd *async_cmd) w1_unref_slave(sl); else atomic_dec(&dev->refcnt); - dev->portid = 0; + dev->priv = NULL; mutex_unlock(&dev->bus_mutex); mutex_lock(&dev->list_mutex); list_del(&async_cmd->async_entry); mutex_unlock(&dev->list_mutex); - if (atomic_sub_return(1, &node->block->refcnt) == 0) - kfree(node->block); + w1_unref_block(node->block); +} + +static void w1_list_count_cmds(struct w1_netlink_msg *msg, int *cmd_count, + u16 *slave_len) +{ + struct w1_netlink_cmd *cmd = (struct w1_netlink_cmd *)msg->data; + u16 mlen = msg->len; + u16 len; + int slave_list = 0; + while (mlen) { + if (cmd->len + sizeof(struct w1_netlink_cmd) > mlen) + break; + + switch (cmd->cmd) { + case W1_CMD_SEARCH: + case W1_CMD_ALARM_SEARCH: + case W1_CMD_LIST_SLAVES: + ++slave_list; + } + ++*cmd_count; + len = sizeof(*cmd) + cmd->len; + cmd = (struct w1_netlink_cmd *)((u8 *)cmd + len); + mlen -= len; + } + + if (slave_list) { + struct w1_master *dev = w1_search_master_id(msg->id.mst.id); + if (dev) { + /* Bytes, and likely an overstimate, and if it isn't + * the results can still be split between packets. + */ + *slave_len += sizeof(struct w1_reg_num) * slave_list * + (dev->slave_count + dev->max_slave_count); + /* search incremented it */ + atomic_dec(&dev->refcnt); + } + } } -static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) +static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) { - struct w1_netlink_msg *m = (struct w1_netlink_msg *)(msg + 1); + struct w1_netlink_msg *msg = (struct w1_netlink_msg *)(cn + 1); struct w1_slave *sl; struct w1_master *dev; u16 msg_len; + u16 slave_len = 0; int err = 0; struct w1_cb_block *block = NULL; struct w1_cb_node *node = NULL; int node_count = 0; + int cmd_count = 0; + + /* If any unknown flag is set let the application know, that way + * applications can detect the absence of features in kernels that + * don't know about them. http://lwn.net/Articles/587527/ + */ + if (cn->flags & ~(W1_CN_BUNDLE)) { + w1_netlink_send_error(cn, msg, nsp->portid, -EINVAL); + return; + } /* Count the number of master or slave commands there are to allocate * space for one cb_node each. */ - msg_len = msg->len; + msg_len = cn->len; while (msg_len && !err) { - if (m->len + sizeof(struct w1_netlink_msg) > msg_len) { + if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { err = -E2BIG; break; } - if (m->type == W1_MASTER_CMD || m->type == W1_SLAVE_CMD) + /* count messages for nodes and allocate any additional space + * required for slave lists + */ + if (msg->type == W1_MASTER_CMD || msg->type == W1_SLAVE_CMD) { ++node_count; + w1_list_count_cmds(msg, &cmd_count, &slave_len); + } - msg_len -= sizeof(struct w1_netlink_msg) + m->len; - m = (struct w1_netlink_msg *)(((u8 *)m) + - sizeof(struct w1_netlink_msg) + m->len); + msg_len -= sizeof(struct w1_netlink_msg) + msg->len; + msg = (struct w1_netlink_msg *)(((u8 *)msg) + + sizeof(struct w1_netlink_msg) + msg->len); } - m = (struct w1_netlink_msg *)(msg + 1); + msg = (struct w1_netlink_msg *)(cn + 1); if (node_count) { - /* msg->len doesn't include itself */ - long size = sizeof(struct w1_cb_block) + msg->len + - node_count*sizeof(struct w1_cb_node); - block = kmalloc(size, GFP_KERNEL); + int size; + u16 reply_size = sizeof(*cn) + cn->len + slave_len; + if (cn->flags & W1_CN_BUNDLE) { + /* bundling duplicats some of the messages */ + reply_size += 2 * cmd_count * (sizeof(struct cn_msg) + + sizeof(struct w1_netlink_msg) + + sizeof(struct w1_netlink_cmd)); + } + reply_size = MIN(CONNECTOR_MAX_MSG_SIZE, reply_size); + + /* allocate space for the block, a copy of the original message, + * one node per cmd to point into the original message, + * space for replies which is the original message size plus + * space for any list slave data and status messages + * cn->len doesn't include itself which is part of the block + * */ + size = /* block + original message */ + sizeof(struct w1_cb_block) + sizeof(*cn) + cn->len + + /* space for nodes */ + node_count * sizeof(struct w1_cb_node) + + /* replies */ + sizeof(struct cn_msg) + reply_size; + block = kzalloc(size, GFP_KERNEL); if (!block) { - w1_netlink_send_error(msg, m, NULL, nsp->portid, - -ENOMEM); + /* if the system is already out of memory, + * (A) will this work, and (B) would it be better + * to not try? + */ + w1_netlink_send_error(cn, msg, nsp->portid, -ENOMEM); return; } atomic_set(&block->refcnt, 1); block->portid = nsp->portid; - memcpy(&block->msg, msg, sizeof(*msg) + msg->len); - node = (struct w1_cb_node *)((u8 *)block->msg.data + msg->len); + memcpy(&block->request_cn, cn, sizeof(*cn) + cn->len); + node = (struct w1_cb_node *)(block->request_cn.data + cn->len); + + /* Sneeky, when not bundling, reply_size is the allocated space + * required for the reply, cn_msg isn't part of maxlen so + * it should be reply_size - sizeof(struct cn_msg), however + * when checking if there is enough space, w1_reply_make_space + * is called with the full message size including cn_msg, + * because it isn't known at that time if an additional cn_msg + * will need to be allocated. So an extra cn_msg is added + * above in "size". + */ + block->maxlen = reply_size; + block->first_cn = (struct cn_msg *)(node + node_count); + memset(block->first_cn, 0, sizeof(*block->first_cn)); } - msg_len = msg->len; + msg_len = cn->len; while (msg_len && !err) { dev = NULL; sl = NULL; - if (m->len + sizeof(struct w1_netlink_msg) > msg_len) { + if (msg->len + sizeof(struct w1_netlink_msg) > msg_len) { err = -E2BIG; break; } /* execute on this thread, no need to process later */ - if (m->type == W1_LIST_MASTERS) { - err = w1_process_command_root(msg, m, nsp->portid); + if (msg->type == W1_LIST_MASTERS) { + err = w1_process_command_root(cn, nsp->portid); goto out_cont; } /* All following message types require additional data, * check here before references are taken. */ - if (!m->len) { + if (!msg->len) { err = -EPROTO; goto out_cont; } - /* both search calls take reference counts */ - if (m->type == W1_MASTER_CMD) { - dev = w1_search_master_id(m->id.mst.id); - } else if (m->type == W1_SLAVE_CMD) { - sl = w1_search_slave((struct w1_reg_num *)m->id.id); + /* both search calls take references */ + if (msg->type == W1_MASTER_CMD) { + dev = w1_search_master_id(msg->id.mst.id); + } else if (msg->type == W1_SLAVE_CMD) { + sl = w1_search_slave((struct w1_reg_num *)msg->id.id); if (sl) dev = sl->master; } else { printk(KERN_NOTICE - "%s: msg: %x.%x, wrong type: %u, len: %u.\n", - __func__, msg->id.idx, msg->id.val, - m->type, m->len); + "%s: cn: %x.%x, wrong type: %u, len: %u.\n", + __func__, cn->id.idx, cn->id.val, + msg->type, msg->len); err = -EPROTO; goto out_cont; } @@ -549,8 +698,8 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) atomic_inc(&block->refcnt); node->async.cb = w1_process_cb; node->block = block; - node->m = (struct w1_netlink_msg *)((u8 *)&block->msg + - (size_t)((u8 *)m - (u8 *)msg)); + node->msg = (struct w1_netlink_msg *)((u8 *)&block->request_cn + + (size_t)((u8 *)msg - (u8 *)cn)); node->sl = sl; node->dev = dev; @@ -561,11 +710,15 @@ static void w1_cn_callback(struct cn_msg *msg, struct netlink_skb_parms *nsp) ++node; out_cont: + /* Can't queue because that modifies block and another + * thread could be processing the messages by now and + * there isn't a lock, send directly. + */ if (err) - w1_netlink_send_error(msg, m, NULL, nsp->portid, err); - msg_len -= sizeof(struct w1_netlink_msg) + m->len; - m = (struct w1_netlink_msg *)(((u8 *)m) + - sizeof(struct w1_netlink_msg) + m->len); + w1_netlink_send_error(cn, msg, nsp->portid, err); + msg_len -= sizeof(struct w1_netlink_msg) + msg->len; + msg = (struct w1_netlink_msg *)(((u8 *)msg) + + sizeof(struct w1_netlink_msg) + msg->len); /* * Let's allow requests for nonexisting devices. @@ -573,8 +726,8 @@ out_cont: if (err == -ENODEV) err = 0; } - if (block && atomic_sub_return(1, &block->refcnt) == 0) - kfree(block); + if (block) + w1_unref_block(block); } int w1_init_netlink(void) @@ -591,7 +744,7 @@ void w1_fini_netlink(void) cn_del_callback(&w1_id); } #else -void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *msg) +void w1_netlink_send(struct w1_master *dev, struct w1_netlink_msg *cn) { } diff --git a/drivers/w1/w1_netlink.h b/drivers/w1/w1_netlink.h index 1e9504e67650..c99a9ce05e62 100644 --- a/drivers/w1/w1_netlink.h +++ b/drivers/w1/w1_netlink.h @@ -27,6 +27,17 @@ #include "w1.h" +/** + * enum w1_cn_msg_flags - bitfield flags for struct cn_msg.flags + * + * @W1_CN_BUNDLE: Request bundling replies into fewer messagse. Be prepared + * to handle multiple struct cn_msg, struct w1_netlink_msg, and + * struct w1_netlink_cmd in one packet. + */ +enum w1_cn_msg_flags { + W1_CN_BUNDLE = 1, +}; + /** * enum w1_netlink_message_types - message type * @@ -49,6 +60,19 @@ enum w1_netlink_message_types { W1_LIST_MASTERS, }; +/** + * struct w1_netlink_msg - holds w1 message type, id, and result + * + * @type: one of enum w1_netlink_message_types + * @status: kernel feedback for success 0 or errno failure value + * @len: length of data following w1_netlink_msg + * @id: union holding master bus id (msg.id) and slave device id (id[8]). + * @data: start address of any following data + * + * The base message structure for w1 messages over netlink. + * The netlink connector data sequence is, struct nlmsghdr, struct cn_msg, + * then one or more struct w1_netlink_msg (each with optional data). + */ struct w1_netlink_msg { __u8 type; @@ -66,6 +90,7 @@ struct w1_netlink_msg /** * enum w1_commands - commands available for master or slave operations + * * @W1_CMD_READ: read len bytes * @W1_CMD_WRITE: write len bytes * @W1_CMD_SEARCH: initiate a standard search, returns only the slave @@ -93,6 +118,17 @@ enum w1_commands { W1_CMD_MAX }; +/** + * struct w1_netlink_cmd - holds the command and data + * + * @cmd: one of enum w1_commands + * @res: reserved + * @len: length of data following w1_netlink_cmd + * @data: start address of any following data + * + * One or more struct w1_netlink_cmd is placed starting at w1_netlink_msg.data + * each with optional data. + */ struct w1_netlink_cmd { __u8 cmd; -- cgit v1.2.3 From a0f104644ec27ce5bbb36e950eb426dba9a3ad44 Mon Sep 17 00:00:00 2001 From: Alexey Khoroshilov Date: Wed, 7 May 2014 01:26:04 +0400 Subject: w1: do not unlock unheld list_mutex in __w1_remove_master_device() w1_process_callbacks() expects to be called with dev->list_mutex held, but it is the fact only in w1_process(). __w1_remove_master_device() calls w1_process_callbacks() after it releases list_mutex. The patch fixes __w1_remove_master_device() to acquire list_mutex for w1_process_callbacks(). Found by Linux Driver Verification project (linuxtesting.org). Signed-off-by: Alexey Khoroshilov Acked-by: David Fries Acked-by: Evgeniy Polyakov Cc: stable # 3.15 Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1.c | 2 ++ drivers/w1/w1_int.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index ff52618cafbe..5d7341520544 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -1078,6 +1078,8 @@ static void w1_search_process(struct w1_master *dev, u8 search_type) * w1_process_callbacks() - execute each dev->async_list callback entry * @dev: w1_master device * + * The w1 master list_mutex must be held. + * * Return: 1 if there were commands to executed 0 otherwise */ int w1_process_callbacks(struct w1_master *dev) diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 9b084db739c7..728039d2efe1 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -219,9 +219,13 @@ void __w1_remove_master_device(struct w1_master *dev) if (msleep_interruptible(1000)) flush_signals(current); + mutex_lock(&dev->list_mutex); w1_process_callbacks(dev); + mutex_unlock(&dev->list_mutex); } + mutex_lock(&dev->list_mutex); w1_process_callbacks(dev); + mutex_unlock(&dev->list_mutex); memset(&msg, 0, sizeof(msg)); msg.id.mst.id = dev->id; -- cgit v1.2.3 From ca3c61f358d8e5a4b2732d6aa81ac46f677e69f0 Mon Sep 17 00:00:00 2001 From: Daeseok Youn Date: Thu, 22 May 2014 09:46:12 +0900 Subject: drivers: uio_dmem_genirq: Fix memory leak in uio_dmem_genirq_probe() When platform_get_irq() is failed after "priv" allocated, it need to free "priv". But the label of bad0 doesn't try to free about "priv". So this patch changes that lable to "bad1". But "bad1" has pm_runtime_disable() call, this function should be called when uio_register_device() is failed. So it is moved into handling error for uio_register_device(). Signed-off-by: Daeseok Youn Signed-off-by: Hans J. Koch Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_dmem_genirq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/uio/uio_dmem_genirq.c b/drivers/uio/uio_dmem_genirq.c index 1270f3b26139..8d0bba469566 100644 --- a/drivers/uio/uio_dmem_genirq.c +++ b/drivers/uio/uio_dmem_genirq.c @@ -204,7 +204,7 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) ret = platform_get_irq(pdev, 0); if (ret < 0) { dev_err(&pdev->dev, "failed to get IRQ\n"); - goto bad0; + goto bad1; } uioinfo->irq = ret; } @@ -275,6 +275,7 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) ret = uio_register_device(&pdev->dev, priv->uioinfo); if (ret) { dev_err(&pdev->dev, "unable to register uio device\n"); + pm_runtime_disable(&pdev->dev); goto bad1; } @@ -282,7 +283,6 @@ static int uio_dmem_genirq_probe(struct platform_device *pdev) return 0; bad1: kfree(priv); - pm_runtime_disable(&pdev->dev); bad0: /* kfree uioinfo for OF */ if (pdev->dev.of_node) -- cgit v1.2.3 From ddb09754e6c7239e302c7b675df9bbd415f8de5d Mon Sep 17 00:00:00 2001 From: Bin Wang Date: Tue, 25 Mar 2014 13:52:06 +0800 Subject: uio: fix vma io range check in mmap the vma range size is always page size aligned in mmap, while the real io space range may not be page aligned, thus leading to range check failure in the uio_mmap_physical(). for example, in a case of io range size "mem->size == 1KB", and we have (vma->vm_end - vma->vm_start) == 4KB, due to "len" is aligned to page size in do_mmap_pgoff(). now fix this issue by align mem->size to page size in the check. Signed-off-by: Bin Wang Signed-off-by: Nobuhiro Iwamatsu Cc: stable Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index a673e5b6a2e0..e371f5af11f5 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -655,7 +655,7 @@ static int uio_mmap_physical(struct vm_area_struct *vma) if (mem->addr & ~PAGE_MASK) return -ENODEV; - if (vma->vm_end - vma->vm_start > mem->size) + if (vma->vm_end - vma->vm_start > PAGE_ALIGN(mem->size)) return -EINVAL; vma->vm_ops = &uio_physical_vm_ops; -- cgit v1.2.3 From 84b3294a40c87e5c8bdaf05d9d3c3aff7e320453 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Wed, 7 May 2014 16:51:28 +0300 Subject: mei: fix memory leak of mei_clients array we never freed the mei_clients array on driver shutdown only on reset add mei_hbm_reset function that wraps the hbm cleanup Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hbm.c | 46 +++++++++++++++++++++++++++++----------------- drivers/misc/mei/hbm.h | 1 + drivers/misc/mei/init.c | 2 +- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/drivers/misc/mei/hbm.c b/drivers/misc/mei/hbm.c index b9a4bb5921f1..804106209d76 100644 --- a/drivers/misc/mei/hbm.c +++ b/drivers/misc/mei/hbm.c @@ -59,6 +59,34 @@ static int mei_cl_conn_status_to_errno(enum mei_cl_connect_status status) } } +/** + * mei_hbm_idle - set hbm to idle state + * + * @dev: the device structure + */ +void mei_hbm_idle(struct mei_device *dev) +{ + dev->init_clients_timer = 0; + dev->hbm_state = MEI_HBM_IDLE; +} + +/** + * mei_hbm_reset - reset hbm counters and book keeping data structurs + * + * @dev: the device structure + */ +void mei_hbm_reset(struct mei_device *dev) +{ + dev->me_clients_num = 0; + dev->me_client_presentation_num = 0; + dev->me_client_index = 0; + + kfree(dev->me_clients); + dev->me_clients = NULL; + + mei_hbm_idle(dev); +} + /** * mei_hbm_me_cl_allocate - allocates storage for me clients * @@ -71,9 +99,7 @@ static int mei_hbm_me_cl_allocate(struct mei_device *dev) struct mei_me_client *clients; int b; - dev->me_clients_num = 0; - dev->me_client_presentation_num = 0; - dev->me_client_index = 0; + mei_hbm_reset(dev); /* count how many ME clients we have */ for_each_set_bit(b, dev->me_clients_map, MEI_CLIENTS_MAX) @@ -82,9 +108,6 @@ static int mei_hbm_me_cl_allocate(struct mei_device *dev) if (dev->me_clients_num == 0) return 0; - kfree(dev->me_clients); - dev->me_clients = NULL; - dev_dbg(&dev->pdev->dev, "memory allocation for ME clients size=%ld.\n", dev->me_clients_num * sizeof(struct mei_me_client)); /* allocate storage for ME clients representation */ @@ -135,17 +158,6 @@ bool mei_hbm_cl_addr_equal(struct mei_cl *cl, void *buf) } -/** - * mei_hbm_idle - set hbm to idle state - * - * @dev: the device structure - */ -void mei_hbm_idle(struct mei_device *dev) -{ - dev->init_clients_timer = 0; - dev->hbm_state = MEI_HBM_IDLE; -} - int mei_hbm_start_wait(struct mei_device *dev) { int ret; diff --git a/drivers/misc/mei/hbm.h b/drivers/misc/mei/hbm.h index 8e39cee408d0..683eb2835cec 100644 --- a/drivers/misc/mei/hbm.h +++ b/drivers/misc/mei/hbm.h @@ -50,6 +50,7 @@ static inline void mei_hbm_hdr(struct mei_msg_hdr *hdr, size_t length) } void mei_hbm_idle(struct mei_device *dev); +void mei_hbm_reset(struct mei_device *dev); int mei_hbm_start_req(struct mei_device *dev); int mei_hbm_start_wait(struct mei_device *dev); int mei_hbm_cl_flow_control_req(struct mei_device *dev, struct mei_cl *cl); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index b3f70eb17934..510f378b1f06 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -122,8 +122,8 @@ int mei_reset(struct mei_device *dev) mei_amthif_reset_params(dev); } + mei_hbm_reset(dev); - dev->me_clients_num = 0; dev->rd_msg_hdr = 0; dev->wd_pending = false; -- cgit v1.2.3 From b04ada92ffaabb868497a1fce8e4f6bf74e5488f Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 12 May 2014 12:19:39 +0300 Subject: mei: me: fix hw ready reset flow We cleared H_RST for H_CSR on spurious interrupt generated when ME_RDY while cleared and not while ME_RDY is set. The spurious interrupt is not delivered on all platforms in this case the driver may fail to initialize. Cc: stable@vger.kernel.org Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index a31bc4c36cca..ac76a6377794 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -181,6 +181,9 @@ static void mei_me_hw_reset_release(struct mei_device *dev) hcsr |= H_IG; hcsr &= ~H_RST; mei_hcsr_set(hw, hcsr); + + /* complete this write before we set host ready on another CPU */ + mmiowb(); } /** * mei_me_hw_reset - resets fw via mei csr register. @@ -218,6 +221,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) static void mei_me_host_set_ready(struct mei_device *dev) { struct mei_me_hw *hw = to_me_hw(dev); + hw->host_hw_state = mei_hcsr_read(hw); hw->host_hw_state |= H_IE | H_IG | H_RDY; mei_hcsr_set(hw, hw->host_hw_state); } @@ -646,14 +650,13 @@ irqreturn_t mei_me_irq_thread_handler(int irq, void *dev_id) /* check if we need to start the dev */ if (!mei_host_is_ready(dev)) { if (mei_hw_is_ready(dev)) { + mei_me_hw_reset_release(dev); dev_dbg(&dev->pdev->dev, "we need to start the dev.\n"); dev->recvd_hw_ready = true; wake_up_interruptible(&dev->wait_hw_ready); } else { - - dev_dbg(&dev->pdev->dev, "Reset Completed.\n"); - mei_me_hw_reset_release(dev); + dev_dbg(&dev->pdev->dev, "Spurious Interrupt\n"); } goto end; } -- cgit v1.2.3 From 07cd7be3d92eeeae1f92a017f2cfe4fdd9256526 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 12 May 2014 12:19:40 +0300 Subject: mei: me: drop harmful wait optimization It my take time till ME_RDY will be cleared after the reset, so we cannot check the bit before we got the interrupt Cc: stable@vger.kernel.org Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index ac76a6377794..031efcfed39e 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -203,6 +203,7 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) else hcsr &= ~H_IE; + dev->recvd_hw_ready = false; mei_me_reg_write(hw, H_CSR, hcsr); if (intr_enable == false) @@ -254,10 +255,7 @@ static bool mei_me_hw_is_ready(struct mei_device *dev) static int mei_me_hw_ready_wait(struct mei_device *dev) { int err; - if (mei_me_hw_is_ready(dev)) - return 0; - dev->recvd_hw_ready = false; mutex_unlock(&dev->device_lock); err = wait_event_interruptible_timeout(dev->wait_hw_ready, dev->recvd_hw_ready, -- cgit v1.2.3 From c40765d919d25d2d44d99c4ce39e48808f137e1e Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 12 May 2014 12:19:41 +0300 Subject: mei: me: read H_CSR after asserting reset According the spec the host should read H_CSR again after asserting reset H_RST to ensure that reset was read by the firmware Cc: stable@vger.kernel.org Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 031efcfed39e..016ad88a2887 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -206,6 +206,18 @@ static int mei_me_hw_reset(struct mei_device *dev, bool intr_enable) dev->recvd_hw_ready = false; mei_me_reg_write(hw, H_CSR, hcsr); + /* + * Host reads the H_CSR once to ensure that the + * posted write to H_CSR completes. + */ + hcsr = mei_hcsr_read(hw); + + if ((hcsr & H_RST) == 0) + dev_warn(&dev->pdev->dev, "H_RST is not set = 0x%08X", hcsr); + + if ((hcsr & H_RDY) == H_RDY) + dev_warn(&dev->pdev->dev, "H_RDY is not cleared 0x%08X", hcsr); + if (intr_enable == false) mei_me_hw_reset_release(dev); -- cgit v1.2.3 From 8d929d4862fdfc4a524fd4c799b8dfa3b187fe8c Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Tue, 13 May 2014 01:30:53 +0300 Subject: mei: add per device configuration Add mei_cfg structure that holds per device configuration data and hooks, as the first step we add firmware status register offsets Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 34 ++++++++++++++++++-- drivers/misc/mei/hw-me.h | 7 ++++- drivers/misc/mei/hw-txe.c | 17 ++++++++-- drivers/misc/mei/hw-txe.h | 5 ++- drivers/misc/mei/init.c | 24 ++++++++++++++- drivers/misc/mei/mei_dev.h | 27 ++++++++++++---- drivers/misc/mei/pci-me.c | 77 ++++++++++++++++++++++++---------------------- drivers/misc/mei/pci-txe.c | 5 +-- 8 files changed, 144 insertions(+), 52 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 016ad88a2887..ed081182f973 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -792,14 +792,44 @@ static const struct mei_hw_ops mei_me_hw_ops = { .read = mei_me_read_slots }; +#define MEI_CFG_LEGACY_HFS \ + .fw_status.count = 0 + +#define MEI_CFG_ICH_HFS \ + .fw_status.count = 1, \ + .fw_status.status[0] = PCI_CFG_HFS_1 + +#define MEI_CFG_PCH_HFS \ + .fw_status.count = 2, \ + .fw_status.status[0] = PCI_CFG_HFS_1, \ + .fw_status.status[1] = PCI_CFG_HFS_2 + + +/* ICH Legacy devices */ +const struct mei_cfg mei_me_legacy_cfg = { + MEI_CFG_LEGACY_HFS, +}; + +/* ICH devices */ +const struct mei_cfg mei_me_ich_cfg = { + MEI_CFG_ICH_HFS, +}; + +/* PCH devices */ +const struct mei_cfg mei_me_pch_cfg = { + MEI_CFG_PCH_HFS, +}; + /** * mei_me_dev_init - allocates and initializes the mei device structure * * @pdev: The pci device structure + * @cfg: per device generation config * * returns The mei_device_device pointer on success, NULL on failure. */ -struct mei_device *mei_me_dev_init(struct pci_dev *pdev) +struct mei_device *mei_me_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg) { struct mei_device *dev; @@ -808,7 +838,7 @@ struct mei_device *mei_me_dev_init(struct pci_dev *pdev) if (!dev) return NULL; - mei_device_init(dev); + mei_device_init(dev, cfg); dev->ops = &mei_me_hw_ops; diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 0a75ab85b3a2..473aafbc4023 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -38,7 +38,12 @@ struct mei_me_hw { #define to_me_hw(dev) (struct mei_me_hw *)((dev)->hw) -struct mei_device *mei_me_dev_init(struct pci_dev *pdev); +extern const struct mei_cfg mei_me_legacy_cfg; +extern const struct mei_cfg mei_me_ich_cfg; +extern const struct mei_cfg mei_me_pch_cfg; + +struct mei_device *mei_me_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg); int mei_me_pg_set_sync(struct mei_device *dev); int mei_me_pg_unset_sync(struct mei_device *dev); diff --git a/drivers/misc/mei/hw-txe.c b/drivers/misc/mei/hw-txe.c index 455a6a56b82b..93273783dec5 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -1104,14 +1104,27 @@ static const struct mei_hw_ops mei_txe_hw_ops = { }; +#define MEI_CFG_TXE_FW_STS \ + .fw_status.count = 2, \ + .fw_status.status[0] = PCI_CFG_TXE_FW_STS0, \ + .fw_status.status[1] = PCI_CFG_TXE_FW_STS1 + +const struct mei_cfg mei_txe_cfg = { + MEI_CFG_TXE_FW_STS, +}; + + /** * mei_txe_dev_init - allocates and initializes txe hardware specific structure * * @pdev - pci device + * @cfg - per device generation config + * * returns struct mei_device * on success or NULL; * */ -struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg) { struct mei_device *dev; struct mei_txe_hw *hw; @@ -1121,7 +1134,7 @@ struct mei_device *mei_txe_dev_init(struct pci_dev *pdev) if (!dev) return NULL; - mei_device_init(dev); + mei_device_init(dev, cfg); hw = to_txe_hw(dev); diff --git a/drivers/misc/mei/hw-txe.h b/drivers/misc/mei/hw-txe.h index e8dd2d165c25..e244af79167f 100644 --- a/drivers/misc/mei/hw-txe.h +++ b/drivers/misc/mei/hw-txe.h @@ -61,7 +61,10 @@ static inline struct mei_device *hw_txe_to_mei(struct mei_txe_hw *hw) return container_of((void *)hw, struct mei_device, hw); } -struct mei_device *mei_txe_dev_init(struct pci_dev *pdev); +extern const struct mei_cfg mei_txe_cfg; + +struct mei_device *mei_txe_dev_init(struct pci_dev *pdev, + const struct mei_cfg *cfg); irqreturn_t mei_txe_irq_quick_handler(int irq, void *dev_id); irqreturn_t mei_txe_irq_thread_handler(int irq, void *dev_id); diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index 510f378b1f06..006929222481 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c @@ -330,7 +330,28 @@ bool mei_write_is_idle(struct mei_device *dev) } EXPORT_SYMBOL_GPL(mei_write_is_idle); -void mei_device_init(struct mei_device *dev) +int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status) +{ + int i; + const struct mei_fw_status *fw_src = &dev->cfg->fw_status; + + if (!fw_status) + return -EINVAL; + + fw_status->count = fw_src->count; + for (i = 0; i < fw_src->count && i < MEI_FW_STATUS_MAX; i++) { + int ret; + ret = pci_read_config_dword(dev->pdev, + fw_src->status[i], &fw_status->status[i]); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(mei_fw_status); + +void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg) { /* setup our list array */ INIT_LIST_HEAD(&dev->file_list); @@ -368,6 +389,7 @@ void mei_device_init(struct mei_device *dev) bitmap_set(dev->host_clients_map, 0, 1); dev->pg_event = MEI_PG_EVENT_IDLE; + dev->cfg = cfg; } EXPORT_SYMBOL_GPL(mei_device_init); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 1aaaf0b6681c..909d13de17e9 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -379,6 +379,22 @@ enum mei_pg_state { MEI_PG_ON = 1, }; +/* + * mei_cfg + * + * @fw_status - FW status + */ +struct mei_cfg { + const struct mei_fw_status fw_status; +}; + + +#define MEI_PCI_DEVICE(dev, cfg) \ + .vendor = PCI_VENDOR_ID_INTEL, .device = (dev), \ + .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, \ + .driver_data = (kernel_ulong_t)&(cfg) + + /** * struct mei_device - MEI private device struct @@ -390,6 +406,7 @@ enum mei_pg_state { * @hbuf_depth - depth of hardware host/write buffer is slots * @hbuf_is_ready - query if the host host/write buffer is ready * @wr_msg - the buffer for hbm control messages + * @cfg - per device generation config and ops */ struct mei_device { struct pci_dev *pdev; /* pointer to pci device struct */ @@ -500,6 +517,7 @@ struct mei_device { const struct mei_hw_ops *ops; + const struct mei_cfg *cfg; char hw[0] __aligned(sizeof(void *)); }; @@ -532,7 +550,7 @@ static inline u32 mei_slots2data(int slots) /* * mei init function prototypes */ -void mei_device_init(struct mei_device *dev); +void mei_device_init(struct mei_device *dev, const struct mei_cfg *cfg); int mei_reset(struct mei_device *dev); int mei_start(struct mei_device *dev); int mei_restart(struct mei_device *dev); @@ -611,6 +629,7 @@ void mei_watchdog_unregister(struct mei_device *dev); * Register Access Function */ + static inline void mei_hw_config(struct mei_device *dev) { dev->ops->hw_config(dev); @@ -698,11 +717,7 @@ static inline int mei_count_full_read_slots(struct mei_device *dev) return dev->ops->rdbuf_full_slots(dev); } -static inline int mei_fw_status(struct mei_device *dev, - struct mei_fw_status *fw_status) -{ - return dev->ops->fw_status(dev, fw_status); -} +int mei_fw_status(struct mei_device *dev, struct mei_fw_status *fw_status); #define FW_STS_FMT "%08X %08X" #define FW_STS_PRM(fw_status) \ diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 88516b02c685..6cb9819ac5d5 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -44,42 +44,44 @@ /* mei_pci_tbl - PCI Device ID Table */ static const struct pci_device_id mei_me_pci_tbl[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82946GZ)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82Q965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82G965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GM965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_82GME965)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q35)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82G33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82Q33)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_82X38)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_3200)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_6)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_7)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_8)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_9)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9_10)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH9M_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_ICH10_4)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_IBXPK_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_CPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PBG_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_1)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_2)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_PPT_3)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_H)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_W)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_LP)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_LPT_HR)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MEI_DEV_ID_WPT_LP)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82946GZ, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82G35, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82Q965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82G965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82GM965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_82GME965, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q35, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82G33, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82Q33, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_82X38, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_3200, mei_me_legacy_cfg)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_6, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_7, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_8, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_9, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9_10, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_1, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_2, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_3, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH9M_4, mei_me_legacy_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_1, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_2, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_3, mei_me_ich_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICH10_4, mei_me_ich_cfg)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_2, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CPT_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PBG_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_2, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_3, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_H, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_W, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_LP, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_HR, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP, mei_me_pch_cfg)}, /* required last entry */ {0, } @@ -143,6 +145,7 @@ no_mei: */ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { + const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); struct mei_device *dev; struct mei_me_hw *hw; int err; @@ -183,7 +186,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* allocates and initializes the mei dev structure */ - dev = mei_me_dev_init(pdev); + dev = mei_me_dev_init(pdev, cfg); if (!dev) { err = -ENOMEM; goto release_regions; diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 2c3f5625a04e..2343c6236df9 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -36,7 +36,7 @@ #include "hw-txe.h" static const struct pci_device_id mei_txe_pci_tbl[] = { - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x0F18)}, /* Baytrail */ + {MEI_PCI_DEVICE(0x0F18, mei_txe_cfg)}, /* Baytrail */ {0, } }; MODULE_DEVICE_TABLE(pci, mei_txe_pci_tbl); @@ -69,6 +69,7 @@ static void mei_txe_pci_iounmap(struct pci_dev *pdev, struct mei_txe_hw *hw) */ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { + const struct mei_cfg *cfg = (struct mei_cfg *)(ent->driver_data); struct mei_device *dev; struct mei_txe_hw *hw; int err; @@ -99,7 +100,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) } /* allocates and initializes the mei dev structure */ - dev = mei_txe_dev_init(pdev); + dev = mei_txe_dev_init(pdev, cfg); if (!dev) { err = -ENOMEM; goto release_regions; -- cgit v1.2.3 From c919951d940f28b3b9eb208e289faa27f4bc4678 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Tue, 13 May 2014 01:30:54 +0300 Subject: mei: me: move probe quirk to cfg structure Move quirk FW type detector to cfg structure Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 37 ++++++++++++++++++++++++++++++++++++ drivers/misc/mei/hw-me.h | 2 ++ drivers/misc/mei/mei_dev.h | 2 ++ drivers/misc/mei/pci-me.c | 47 +++++++++++++--------------------------------- 4 files changed, 54 insertions(+), 34 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index ed081182f973..6a2d272cea43 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -792,6 +792,30 @@ static const struct mei_hw_ops mei_me_hw_ops = { .read = mei_me_read_slots }; +static bool mei_me_fw_type_nm(struct pci_dev *pdev) +{ + u32 reg; + pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); + /* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ + return (reg & 0x600) == 0x200; +} + +#define MEI_CFG_FW_NM \ + .quirk_probe = mei_me_fw_type_nm + +static bool mei_me_fw_type_sps(struct pci_dev *pdev) +{ + u32 reg; + /* Read ME FW Status check for SPS Firmware */ + pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); + /* if bits [19:16] = 15, running SPS Firmware */ + return (reg & 0xf0000) == 0xf0000; +} + +#define MEI_CFG_FW_SPS \ + .quirk_probe = mei_me_fw_type_sps + + #define MEI_CFG_LEGACY_HFS \ .fw_status.count = 0 @@ -820,6 +844,19 @@ const struct mei_cfg mei_me_pch_cfg = { MEI_CFG_PCH_HFS, }; + +/* PCH Cougar Point and Patsburg with quirk for Node Manager exclusion */ +const struct mei_cfg mei_me_pch_cpt_pbg_cfg = { + MEI_CFG_PCH_HFS, + MEI_CFG_FW_NM, +}; + +/* PCH Lynx Point with quirk for SPS Firmware exclusion */ +const struct mei_cfg mei_me_lpt_cfg = { + MEI_CFG_PCH_HFS, + MEI_CFG_FW_SPS, +}; + /** * mei_me_dev_init - allocates and initializes the mei device structure * diff --git a/drivers/misc/mei/hw-me.h b/drivers/misc/mei/hw-me.h index 473aafbc4023..12b0f4bbe1f1 100644 --- a/drivers/misc/mei/hw-me.h +++ b/drivers/misc/mei/hw-me.h @@ -41,6 +41,8 @@ struct mei_me_hw { extern const struct mei_cfg mei_me_legacy_cfg; extern const struct mei_cfg mei_me_ich_cfg; extern const struct mei_cfg mei_me_pch_cfg; +extern const struct mei_cfg mei_me_pch_cpt_pbg_cfg; +extern const struct mei_cfg mei_me_lpt_cfg; struct mei_device *mei_me_dev_init(struct pci_dev *pdev, const struct mei_cfg *cfg); diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 909d13de17e9..5c7e990e2f22 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -383,9 +383,11 @@ enum mei_pg_state { * mei_cfg * * @fw_status - FW status + * @quirk_probe - device exclusion quirk */ struct mei_cfg { const struct mei_fw_status fw_status; + bool (*quirk_probe)(struct pci_dev *pdev); }; diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 6cb9819ac5d5..1b46c64a649f 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -72,15 +72,15 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_1, mei_me_pch_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_IBXPK_2, mei_me_pch_cfg)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_CPT_1, mei_me_pch_cfg)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_PBG_1, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_CPT_1, mei_me_pch_cpt_pbg_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_PBG_1, mei_me_pch_cpt_pbg_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_1, mei_me_pch_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_2, mei_me_pch_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_PPT_3, mei_me_pch_cfg)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_H, mei_me_pch_cfg)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_W, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_H, mei_me_lpt_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_W, mei_me_lpt_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_LP, mei_me_pch_cfg)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_HR, mei_me_pch_cfg)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_HR, mei_me_lpt_cfg)}, {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP, mei_me_pch_cfg)}, /* required last entry */ @@ -101,40 +101,21 @@ static inline void mei_me_unset_pm_domain(struct mei_device *dev) {} * mei_quirk_probe - probe for devices that doesn't valid ME interface * * @pdev: PCI device structure - * @ent: entry into pci_device_table + * @cfg: per generation config * * returns true if ME Interface is valid, false otherwise */ static bool mei_me_quirk_probe(struct pci_dev *pdev, - const struct pci_device_id *ent) + const struct mei_cfg *cfg) { - u32 reg; - /* Cougar Point || Patsburg */ - if (ent->device == MEI_DEV_ID_CPT_1 || - ent->device == MEI_DEV_ID_PBG_1) { - pci_read_config_dword(pdev, PCI_CFG_HFS_2, ®); - /* make sure that bit 9 (NM) is up and bit 10 (DM) is down */ - if ((reg & 0x600) == 0x200) - goto no_mei; - } - - /* Lynx Point */ - if (ent->device == MEI_DEV_ID_LPT_H || - ent->device == MEI_DEV_ID_LPT_W || - ent->device == MEI_DEV_ID_LPT_HR) { - /* Read ME FW Status check for SPS Firmware */ - pci_read_config_dword(pdev, PCI_CFG_HFS_1, ®); - /* if bits [19:16] = 15, running SPS Firmware */ - if ((reg & 0xf0000) == 0xf0000) - goto no_mei; + if (cfg->quirk_probe && cfg->quirk_probe(pdev)) { + dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); + return false; } return true; - -no_mei: - dev_info(&pdev->dev, "Device doesn't have valid ME Interface\n"); - return false; } + /** * mei_probe - Device Initialization Routine * @@ -151,10 +132,8 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) int err; - if (!mei_me_quirk_probe(pdev, ent)) { - err = -ENODEV; - goto end; - } + if (!mei_me_quirk_probe(pdev, cfg)) + return -ENODEV; /* enable pci dev */ err = pci_enable_device(pdev); -- cgit v1.2.3 From 1690e35f9b95e0efe063d76a7ab485db3e8d3bc5 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 8 May 2014 16:56:28 +0200 Subject: misc: atmel_pwm: only build for supported platforms There is architecture code in mach-at91 that depends on the CONFIG_ATMEL_PWM symbol in order to call the soc-specific at91_add_device_pwm function. While all of this is about code that will be removed in the future, using DT probing and the PWM framework, we currently get a build failure: arch/arm/mach-at91/built-in.o: In function `at91_pwm_leds': arch/arm/mach-at91/leds.c:88: undefined reference to `at91_add_device_pwm' This patch ensures we only try to build this driver on platforms on which it will build and work. Signed-off-by: Arnd Bergmann Acked-by: Nicolas Ferre Signed-off-by: Greg Kroah-Hartman --- drivers/misc/Kconfig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 1e6df18e8218..742e67901bf5 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -53,7 +53,8 @@ config AD525X_DPOT_SPI config ATMEL_PWM tristate "Atmel AT32/AT91 PWM support" - depends on HAVE_CLK && (AVR32 || ARCH_AT91 || COMPILE_TEST) + depends on HAVE_CLK + depends on AVR32 || AT91SAM9263 || AT91SAM9RL || AT91SAM9G45 help This option enables device driver support for the PWM channels on certain Atmel processors. Pulse Width Modulation is used for -- cgit v1.2.3 From 2e1c951f5da23faaa60224628f44b100ab212d46 Mon Sep 17 00:00:00 2001 From: Geert Uytterhoeven Date: Thu, 8 May 2014 14:09:32 +0200 Subject: drivers: Remove duplicate conditionally included subdirs The "macintosh" and "nfs" subdirectories are already traversed unconditionally, so there's no need to keep the conditional entries. The unconditional "macintosh" entry used to depend on CONFIG_PPC_PMAC, but the dependency was dropped in commit 45941d0481f538324fa21d6450116d13f6e51e91 ("[PATCH] enable mouse button 2+3 emulation for x86 macs"), forgetting the second entry for CONFIG_MAC. The two "nfc" entries were introduced by two separate commits: commit 0329326e85aaa30fb8d427828c718d565c287385 ("NFC: Driver for NXP Semiconductors PN544 NFC chip."), and commit 3e256b8f8dfa309a80b5dece388d85d9a9801a29 ("NFC: add nfc subsystem core"). Signed-off-by: Geert Uytterhoeven Signed-off-by: Greg Kroah-Hartman --- drivers/Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/Makefile b/drivers/Makefile index d05d81b19b50..da72e328f775 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -80,7 +80,6 @@ obj-$(CONFIG_PCCARD) += pcmcia/ obj-$(CONFIG_DIO) += dio/ obj-$(CONFIG_SBUS) += sbus/ obj-$(CONFIG_ZORRO) += zorro/ -obj-$(CONFIG_MAC) += macintosh/ obj-$(CONFIG_ATA_OVER_ETH) += block/aoe/ obj-$(CONFIG_PARIDE) += block/paride/ obj-$(CONFIG_TC) += tc/ @@ -138,7 +137,6 @@ obj-y += clk/ obj-$(CONFIG_MAILBOX) += mailbox/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ -obj-$(CONFIG_NFC) += nfc/ obj-$(CONFIG_IOMMU_SUPPORT) += iommu/ obj-$(CONFIG_REMOTEPROC) += remoteproc/ obj-$(CONFIG_RPMSG) += rpmsg/ -- cgit v1.2.3 From 4ec65b77c64504e178d75aaba6ac96f68837416c Mon Sep 17 00:00:00 2001 From: Johannes Thumshirn Date: Thu, 24 Apr 2014 14:35:25 +0200 Subject: mcb: Add support for shared PCI IRQs Add support for shared PCI IRQs to mcb and mcb-pci. Signed-off-by: Johannes Thumshirn Signed-off-by: Greg Kroah-Hartman --- drivers/mcb/mcb-core.c | 20 +++++++++++++++----- drivers/mcb/mcb-pci.c | 17 ++++++++++++++++- include/linux/mcb.h | 6 +++++- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/drivers/mcb/mcb-core.c b/drivers/mcb/mcb-core.c index bbe12932d404..9018ab83517a 100644 --- a/drivers/mcb/mcb-core.c +++ b/drivers/mcb/mcb-core.c @@ -183,14 +183,14 @@ EXPORT_SYMBOL_GPL(mcb_device_register); * * Allocate a new @mcb_bus. */ -struct mcb_bus *mcb_alloc_bus(void) +struct mcb_bus *mcb_alloc_bus(struct device *carrier) { struct mcb_bus *bus; int bus_nr; bus = kzalloc(sizeof(struct mcb_bus), GFP_KERNEL); if (!bus) - return NULL; + return ERR_PTR(-ENOMEM); bus_nr = ida_simple_get(&mcb_ida, 0, 0, GFP_KERNEL); if (bus_nr < 0) { @@ -200,7 +200,7 @@ struct mcb_bus *mcb_alloc_bus(void) INIT_LIST_HEAD(&bus->children); bus->bus_nr = bus_nr; - + bus->carrier = carrier; return bus; } EXPORT_SYMBOL_GPL(mcb_alloc_bus); @@ -378,6 +378,13 @@ void mcb_release_mem(struct resource *mem) } EXPORT_SYMBOL_GPL(mcb_release_mem); +static int __mcb_get_irq(struct mcb_device *dev) +{ + struct resource *irq = &dev->irq; + + return irq->start; +} + /** * mcb_get_irq() - Get device's IRQ number * @dev: The @mcb_device the IRQ is for @@ -386,9 +393,12 @@ EXPORT_SYMBOL_GPL(mcb_release_mem); */ int mcb_get_irq(struct mcb_device *dev) { - struct resource *irq = &dev->irq; + struct mcb_bus *bus = dev->bus; - return irq->start; + if (bus->get_irq) + return bus->get_irq(dev); + + return __mcb_get_irq(dev); } EXPORT_SYMBOL_GPL(mcb_get_irq); diff --git a/drivers/mcb/mcb-pci.c b/drivers/mcb/mcb-pci.c index 99c742cbfb5b..b59181965643 100644 --- a/drivers/mcb/mcb-pci.c +++ b/drivers/mcb/mcb-pci.c @@ -20,6 +20,15 @@ struct priv { void __iomem *base; }; +static int mcb_pci_get_irq(struct mcb_device *mdev) +{ + struct mcb_bus *mbus = mdev->bus; + struct device *dev = mbus->carrier; + struct pci_dev *pdev = to_pci_dev(dev); + + return pdev->irq; +} + static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct priv *priv; @@ -67,7 +76,13 @@ static int mcb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) pci_set_drvdata(pdev, priv); - priv->bus = mcb_alloc_bus(); + priv->bus = mcb_alloc_bus(&pdev->dev); + if (IS_ERR(priv->bus)) { + ret = PTR_ERR(priv->bus); + goto err_drvdata; + } + + priv->bus->get_irq = mcb_pci_get_irq; ret = chameleon_parse_cells(priv->bus, mapbase, priv->base); if (ret < 0) diff --git a/include/linux/mcb.h b/include/linux/mcb.h index 2db284d14064..ed06e15a36aa 100644 --- a/include/linux/mcb.h +++ b/include/linux/mcb.h @@ -16,6 +16,7 @@ #include struct mcb_driver; +struct mcb_device; /** * struct mcb_bus - MEN Chameleon Bus @@ -23,11 +24,14 @@ struct mcb_driver; * @dev: pointer to carrier device * @children: the child busses * @bus_nr: mcb bus number + * @get_irq: callback to get IRQ number */ struct mcb_bus { struct list_head children; struct device dev; + struct device *carrier; int bus_nr; + int (*get_irq)(struct mcb_device *dev); }; #define to_mcb_bus(b) container_of((b), struct mcb_bus, dev) @@ -105,7 +109,7 @@ extern void mcb_unregister_driver(struct mcb_driver *driver); module_driver(__mcb_driver, mcb_register_driver, mcb_unregister_driver); extern void mcb_bus_add_devices(const struct mcb_bus *bus); extern int mcb_device_register(struct mcb_bus *bus, struct mcb_device *dev); -extern struct mcb_bus *mcb_alloc_bus(void); +extern struct mcb_bus *mcb_alloc_bus(struct device *carrier); extern struct mcb_bus *mcb_bus_get(struct mcb_bus *bus); extern void mcb_bus_put(struct mcb_bus *bus); extern struct mcb_device *mcb_alloc_dev(struct mcb_bus *bus); -- cgit v1.2.3 From 471322a8f66f879ea1f457a7ac4fac50442c6be7 Mon Sep 17 00:00:00 2001 From: "Robert P. J. Day" Date: Fri, 16 May 2014 04:41:09 -0400 Subject: MAINTAINERS: Add miscdevice.h to file list for char/misc drivers. Signed-off-by: Robert P. J. Day Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index e67ea2442041..a794210a3093 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2193,6 +2193,7 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git S: Supported F: drivers/char/* F: drivers/misc/* +F: include/linux/miscdevice.h CHECKPATCH M: Andy Whitcroft -- cgit v1.2.3 From f82dd4b093ead1161770de70515cb11602ac664c Mon Sep 17 00:00:00 2001 From: "Robert P. J. Day" Date: Fri, 16 May 2014 04:36:13 -0400 Subject: miscdevice.h: Simple syntax fix to make pointers consistent. Signed-off-by: Robert P. J. Day Signed-off-by: Greg Kroah-Hartman --- include/linux/miscdevice.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index 51e26f3cd3b3..ee80dd7d9f60 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -64,7 +64,7 @@ struct miscdevice { umode_t mode; }; -extern int misc_register(struct miscdevice * misc); +extern int misc_register(struct miscdevice *misc); extern int misc_deregister(struct miscdevice *misc); #define MODULE_ALIAS_MISCDEV(minor) \ -- cgit v1.2.3 From 63fa80cd582321693474f9cf9203329a2f8a8494 Mon Sep 17 00:00:00 2001 From: Christian Engelmayer Date: Tue, 13 May 2014 22:47:03 +0200 Subject: misc: genwqe: fix uninitialized return value in genwqe_free_sync_sgl() Function genwqe_free_sync_sgl() returns the value of variable 'rc'. 'rc' is only set in the error paths, thus initialize it by 0. Coverity CID 1204242. Signed-off-by: Christian Engelmayer Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index d049d271699c..5babf4013f4b 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -454,7 +454,7 @@ int genwqe_setup_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl, */ int genwqe_free_sync_sgl(struct genwqe_dev *cd, struct genwqe_sgl *sgl) { - int rc; + int rc = 0; struct pci_dev *pci_dev = cd->pci_dev; if (sgl->fpage) { -- cgit v1.2.3 From 8bab797c6e5724a43b7666ad70860712365cdb71 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 9 May 2014 14:59:16 +0300 Subject: applicom: dereferencing NULL on error path This is a static checker fix. The "dev" variable is always NULL after the while statement so we would be dereferencing a NULL pointer here. Fixes: 819a3eba4233 ('[PATCH] applicom: fix error handling') Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/char/applicom.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/char/applicom.c b/drivers/char/applicom.c index 974321a2508d..14790304b84b 100644 --- a/drivers/char/applicom.c +++ b/drivers/char/applicom.c @@ -345,7 +345,6 @@ out: free_irq(apbs[i].irq, &dummy); iounmap(apbs[i].RamIO); } - pci_disable_device(dev); return ret; } -- cgit v1.2.3 From 4a2883ec794dee96c1702ca847a48bfe5c3ab1f0 Mon Sep 17 00:00:00 2001 From: Josh Cartwright Date: Thu, 1 May 2014 16:40:26 -0500 Subject: spmi: of: fixup generic SPMI devicetree binding example The generic SPMI example was missing an equal sign in the assignment of the #size-cells property. Signed-off-by: Josh Cartwright Signed-off-by: Greg Kroah-Hartman --- Documentation/devicetree/bindings/spmi/spmi.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/spmi/spmi.txt b/Documentation/devicetree/bindings/spmi/spmi.txt index 462a42fb3a1e..4bb10d161a27 100644 --- a/Documentation/devicetree/bindings/spmi/spmi.txt +++ b/Documentation/devicetree/bindings/spmi/spmi.txt @@ -26,7 +26,7 @@ Each child node must have one and only one 'reg' entry of type SPMI_USID. reg = <...>; #address-cells = <2>; - #size-cells <0>; + #size-cells = <0>; child@0 { compatible = "..."; -- cgit v1.2.3 From a100d88df1e924e5c9678fabf054d1bae7ab74fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20Kr=C4=8Dm=C3=A1=C5=99?= Date: Tue, 27 May 2014 19:16:20 +0200 Subject: hv: use correct order when freeing monitor_pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We try to free two pages when only one has been allocated. Cleanup path is unlikely, so I haven't found any trace that would fit, but I hope that free_pages_prepare() does catch it. Cc: stable@vger.kernel.org Signed-off-by: Radim Krčmář Reviewed-by: Amos Kong Acked-by: Jason Wang Signed-off-by: Greg Kroah-Hartman --- drivers/hv/connection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hv/connection.c b/drivers/hv/connection.c index 7f10c151632a..e84f4526eb36 100644 --- a/drivers/hv/connection.c +++ b/drivers/hv/connection.c @@ -224,8 +224,8 @@ cleanup: vmbus_connection.int_page = NULL; } - free_pages((unsigned long)vmbus_connection.monitor_pages[0], 1); - free_pages((unsigned long)vmbus_connection.monitor_pages[1], 1); + free_pages((unsigned long)vmbus_connection.monitor_pages[0], 0); + free_pages((unsigned long)vmbus_connection.monitor_pages[1], 0); vmbus_connection.monitor_pages[0] = NULL; vmbus_connection.monitor_pages[1] = NULL; -- cgit v1.2.3