From a288d648715bab6a1ab2b72cb1c1cc79cdc8cb43 Mon Sep 17 00:00:00 2001 From: Richard Fitzgerald Date: Fri, 23 May 2014 12:54:57 +0100 Subject: extcon: arizona: support inverted jack detect switch Add config option for inverted jack detect switch that opens when jack is inserted. Signed-off-by: Richard Fitzgerald [Acked by Lee Jones for MFD part] Acked-by: Lee Jones Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 34 ++++++++++++++++++++++++++-------- include/linux/mfd/arizona/pdata.h | 3 +++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 6c84e3d12043..cf907430a698 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -39,6 +39,11 @@ #define ARIZONA_ACCDET_MODE_HPL 1 #define ARIZONA_ACCDET_MODE_HPR 2 +#define ARIZONA_MICD_CLAMP_MODE_JDL 0x4 +#define ARIZONA_MICD_CLAMP_MODE_JDH 0x5 +#define ARIZONA_MICD_CLAMP_MODE_JDL_GP5H 0x9 +#define ARIZONA_MICD_CLAMP_MODE_JDH_GP5H 0xb + #define ARIZONA_HPDET_MAX 10000 #define HPDET_DEBOUNCE 500 @@ -962,10 +967,16 @@ static irqreturn_t arizona_jackdet(int irq, void *data) if (arizona->pdata.jd_gpio5) { mask = ARIZONA_MICD_CLAMP_STS; - present = 0; + if (arizona->pdata.jd_invert) + present = ARIZONA_MICD_CLAMP_STS; + else + present = 0; } else { mask = ARIZONA_JD1_STS; - present = ARIZONA_JD1_STS; + if (arizona->pdata.jd_invert) + present = 0; + else + present = ARIZONA_JD1_STS; } ret = regmap_read(arizona->regmap, ARIZONA_AOD_IRQ_RAW_STATUS, &val); @@ -1096,6 +1107,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) struct arizona_pdata *pdata = &arizona->pdata; struct arizona_extcon_info *info; unsigned int val; + unsigned int clamp_mode; int jack_irq_fall, jack_irq_rise; int ret, mode, i, j; @@ -1305,15 +1317,21 @@ static int arizona_extcon_probe(struct platform_device *pdev) regmap_write(arizona->regmap, ARIZONA_GPIO5_CTRL, val); - regmap_update_bits(arizona->regmap, - ARIZONA_MICD_CLAMP_CONTROL, - ARIZONA_MICD_CLAMP_MODE_MASK, 0x9); + if (arizona->pdata.jd_invert) + clamp_mode = ARIZONA_MICD_CLAMP_MODE_JDH_GP5H; + else + clamp_mode = ARIZONA_MICD_CLAMP_MODE_JDL_GP5H; } else { - regmap_update_bits(arizona->regmap, - ARIZONA_MICD_CLAMP_CONTROL, - ARIZONA_MICD_CLAMP_MODE_MASK, 0x4); + if (arizona->pdata.jd_invert) + clamp_mode = ARIZONA_MICD_CLAMP_MODE_JDH; + else + clamp_mode = ARIZONA_MICD_CLAMP_MODE_JDL; } + regmap_update_bits(arizona->regmap, + ARIZONA_MICD_CLAMP_CONTROL, + ARIZONA_MICD_CLAMP_MODE_MASK, clamp_mode); + regmap_update_bits(arizona->regmap, ARIZONA_JACK_DETECT_DEBOUNCE, ARIZONA_MICD_CLAMP_DB, diff --git a/include/linux/mfd/arizona/pdata.h b/include/linux/mfd/arizona/pdata.h index 12a5c135c746..4578c72c9b86 100644 --- a/include/linux/mfd/arizona/pdata.h +++ b/include/linux/mfd/arizona/pdata.h @@ -127,6 +127,9 @@ struct arizona_pdata { /** Internal pull on GPIO5 is disabled when used for jack detection */ bool jd_gpio5_nopull; + /** set to true if jackdet contact opens on insert */ + bool jd_invert; + /** Use the headphone detect circuit to identify the accessory */ bool hpdet_acc_id; -- cgit v1.2.3 From 3bc2ac7deffecc52932ee24357f6c33e5e9ce420 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 23 May 2014 11:03:05 +0200 Subject: extcon: palmas: Make of_device_id array const Array of struct of_device_id may be be const as expected by of_match_table field. Signed-off-by: Krzysztof Kozlowski Cc: Graeme Gregory Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-palmas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index 7417ce84eb2d..5efa96c00196 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -278,7 +278,7 @@ static int palmas_usb_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(palmas_pm_ops, palmas_usb_suspend, palmas_usb_resume); -static struct of_device_id of_palmas_match_tbl[] = { +static const struct of_device_id of_palmas_match_tbl[] = { { .compatible = "ti,palmas-usb", }, { .compatible = "ti,palmas-usb-vid", }, { .compatible = "ti,twl6035-usb", }, -- cgit v1.2.3 From e48f9dac3fc9f8abe03755973dcafc17d5351137 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 22 May 2014 14:01:51 +0900 Subject: extcon: Reorder the sequence of extcon device driver alphabetically This patch reorder the sequence of extcon device diver alphabetically to imporbe readability. Signed-off-by: Chanwoo Choi --- drivers/extcon/Kconfig | 28 ++++++++++++++-------------- drivers/extcon/Makefile | 6 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index aebde489c291..9125eba6b3d6 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -14,6 +14,20 @@ if EXTCON comment "Extcon Device Drivers" +config EXTCON_ADC_JACK + tristate "ADC Jack extcon support" + depends on IIO + help + Say Y here to enable extcon device driver based on ADC values. + +config EXTCON_ARIZONA + tristate "Wolfson Arizona EXTCON support" + depends on MFD_ARIZONA && INPUT && SND_SOC + help + Say Y here to enable support for external accessory detection + with Wolfson Arizona devices. These are audio CODECs with + advanced audio accessory detection support. + config EXTCON_GPIO tristate "GPIO extcon support" depends on GPIOLIB @@ -21,12 +35,6 @@ config EXTCON_GPIO Say Y here to enable GPIO based extcon support. Note that GPIO extcon supports single state per extcon instance. -config EXTCON_ADC_JACK - tristate "ADC Jack extcon support" - depends on IIO - help - Say Y here to enable extcon device driver based on ADC values. - config EXTCON_MAX14577 tristate "MAX14577/77836 EXTCON Support" depends on MFD_MAX14577 @@ -55,14 +63,6 @@ config EXTCON_MAX8997 Maxim MAX8997 PMIC. The MAX8997 MUIC is a USB port accessory detector and switch. -config EXTCON_ARIZONA - tristate "Wolfson Arizona EXTCON support" - depends on MFD_ARIZONA && INPUT && SND_SOC - help - Say Y here to enable support for external accessory detection - with Wolfson Arizona devices. These are audio CODECs with - advanced audio accessory detection support. - config EXTCON_PALMAS tristate "Palmas USB EXTCON support" depends on MFD_PALMAS diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index bf7861ec0906..e48abc6d230f 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -1,12 +1,12 @@ -# + # Makefile for external connector class (extcon) devices # obj-$(CONFIG_EXTCON) += extcon-class.o -obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_ADC_JACK) += extcon-adc-jack.o +obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o +obj-$(CONFIG_EXTCON_GPIO) += extcon-gpio.o obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o -obj-$(CONFIG_EXTCON_ARIZONA) += extcon-arizona.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o -- cgit v1.2.3 From 5a8844b596d5a6008b14d2677b426ac7ed7d260b Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 29 May 2014 16:27:51 +0100 Subject: extcon: arizona: Remove duplicate set of input parent device devm_input_allocate_device already sets the parent device to be that passed to it, we also set this manually in arizona_extcon_probe. This patch removes the redundant set from arizona_extcon_probe. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index cf907430a698..21ee055b1b77 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1186,7 +1186,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) info->input->name = "Headset"; info->input->phys = "arizona/extcon"; - info->input->dev.parent = &pdev->dev; if (pdata->num_micd_configs) { info->micd_modes = pdata->micd_configs; -- cgit v1.2.3 From 34602486d048910fa9c360519b9512b53382e60f Mon Sep 17 00:00:00 2001 From: Nikesh Oswal Date: Thu, 29 May 2014 16:27:52 +0100 Subject: extcon: arizona: Use extcon cable API with index of extcon cable instead of string Use extcon cable API instead of state API as it is much more idiomatic. Signed-off-by: Nikesh Oswal Signed-off-by: Charles Keepax [Modify patch title by Chanwoo Choi] Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 21ee055b1b77..f2c36b15a845 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -669,9 +669,8 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(info->edev, - 1 << ARIZONA_CABLE_HEADPHONE, - 1 << ARIZONA_CABLE_HEADPHONE); + ret = extcon_set_cable_state_(info->edev, + ARIZONA_CABLE_HEADPHONE, true); if (ret != 0) dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); @@ -728,9 +727,8 @@ err: ARIZONA_ACCDET_MODE_MASK, ARIZONA_ACCDET_MODE_MIC); /* Just report headphone */ - ret = extcon_update_state(info->edev, - 1 << ARIZONA_CABLE_HEADPHONE, - 1 << ARIZONA_CABLE_HEADPHONE); + ret = extcon_set_cable_state_(info->edev, + ARIZONA_CABLE_HEADPHONE, true); if (ret != 0) dev_err(arizona->dev, "Failed to report headphone: %d\n", ret); @@ -817,9 +815,8 @@ 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, - 1 << ARIZONA_CABLE_MICROPHONE, - 1 << ARIZONA_CABLE_MICROPHONE); + ret = extcon_set_cable_state_(info->edev, + ARIZONA_CABLE_MICROPHONE, true); if (ret != 0) dev_err(arizona->dev, "Headset report failed: %d\n", -- cgit v1.2.3 From e368f525218497a9943b61301148d30382c1e014 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 29 May 2014 16:27:54 +0100 Subject: extcon: arizona: Correct typo to disable regulation for button detection We can use the bypass mode on the MICVDD reg for button detection, as the comment in the code states, however the code was mistakenly disabling bypass. This patch corrects this and allows bypass mode during button detection. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index f2c36b15a845..c6347965c639 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -823,7 +823,7 @@ static void arizona_micd_detect(struct work_struct *work) ret); /* Don't need to regulate for button detection */ - ret = regulator_allow_bypass(info->micvdd, false); + ret = regulator_allow_bypass(info->micvdd, true); if (ret != 0) { dev_err(arizona->dev, "Failed to bypass MICVDD: %d\n", ret); -- cgit v1.2.3 From 24a279b1cae0af0eb4bf0dd8b3ef85956a96ff7a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 30 May 2014 13:19:17 +0100 Subject: extcon: arizona: Update manual headphone detection calculation The higher levels of impedance have a higher minimum value than the first level. As the same value was used for all levels, higher impedances were reported with a very low level of accuracy. This patch applies the approriate lower threshold for each level, whilst we are changing things add a define for the maximum value at each level to improve readability. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index c6347965c639..c2eed30cfc3a 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -329,14 +329,17 @@ static void arizona_stop_mic(struct arizona_extcon_info *info) } static struct { + unsigned int threshold; unsigned int factor_a; unsigned int factor_b; } arizona_hpdet_b_ranges[] = { - { 5528, 362464 }, - { 11084, 6186851 }, - { 11065, 65460395 }, + { 100, 5528, 362464 }, + { 169, 11084, 6186851 }, + { 169, 11065, 65460395 }, }; +#define ARIZONA_HPDET_B_RANGE_MAX 0x3fb + static struct { int min; int max; @@ -391,7 +394,8 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) >> ARIZONA_HP_IMPEDANCE_RANGE_SHIFT; if (range < ARRAY_SIZE(arizona_hpdet_b_ranges) - 1 && - (val < 100 || val >= 0x3fb)) { + (val < arizona_hpdet_b_ranges[range].threshold || + val >= ARIZONA_HPDET_B_RANGE_MAX)) { range++; dev_dbg(arizona->dev, "Moving to HPDET range %d\n", range); @@ -404,7 +408,8 @@ static int arizona_hpdet_read(struct arizona_extcon_info *info) } /* If we go out of range report top of range */ - if (val < 100 || val >= 0x3fb) { + if (val < arizona_hpdet_b_ranges[range].threshold || + val >= ARIZONA_HPDET_B_RANGE_MAX) { dev_dbg(arizona->dev, "Measurement out of range\n"); return ARIZONA_HPDET_MAX; } -- cgit v1.2.3 From ac65a625a0961e7a96f2e5e182073691d0474c04 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Fri, 30 May 2014 10:13:15 +0900 Subject: extcon: Set parent device of extcon device using prameter of devm_extcon_dev_allocate This patch set the parent device of extcon device using first parameter of devm_extco_dev_allocate() to remove duplicate code on all of extcon provider drivers. Signed-off-by: Chanwoo Choi Reported-by: Charles Keepax Tested-by: Charles Keepax Cc: Charles Keepax Cc: Mark Brown Cc: Graeme Gregory Cc: Kishon Vijay Abraham I Cc: Krzysztof Kozlowski --- drivers/extcon/extcon-adc-jack.c | 1 - drivers/extcon/extcon-arizona.c | 1 - drivers/extcon/extcon-class.c | 2 ++ drivers/extcon/extcon-gpio.c | 1 - drivers/extcon/extcon-max77693.c | 1 - drivers/extcon/extcon-max8997.c | 1 - drivers/extcon/extcon-palmas.c | 1 - 7 files changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/extcon/extcon-adc-jack.c b/drivers/extcon/extcon-adc-jack.c index e18f95be3733..d860229e4de1 100644 --- a/drivers/extcon/extcon-adc-jack.c +++ b/drivers/extcon/extcon-adc-jack.c @@ -112,7 +112,6 @@ static int adc_jack_probe(struct platform_device *pdev) 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 */ diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index c2eed30cfc3a..0b1ee9f55378 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1170,7 +1170,6 @@ static int arizona_extcon_probe(struct platform_device *pdev) return -ENOMEM; } info->edev->name = "Headset Jack"; - info->edev->dev.parent = arizona->dev; ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret < 0) { diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c index 18d42c0e4581..4c2f2c543bb7 100644 --- a/drivers/extcon/extcon-class.c +++ b/drivers/extcon/extcon-class.c @@ -645,6 +645,8 @@ struct extcon_dev *devm_extcon_dev_allocate(struct device *dev, return edev; } + edev->dev.parent = dev; + *ptr = edev; devres_add(dev, ptr); diff --git a/drivers/extcon/extcon-gpio.c b/drivers/extcon/extcon-gpio.c index 645b28356819..5b7ec274cb63 100644 --- a/drivers/extcon/extcon-gpio.c +++ b/drivers/extcon/extcon-gpio.c @@ -105,7 +105,6 @@ static int gpio_extcon_probe(struct platform_device *pdev) 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; diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 2c7c3e191591..e9ec6ae8b0d8 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1183,7 +1183,6 @@ static int max77693_muic_probe(struct platform_device *pdev) goto err_irq; } info->edev->name = DEV_NAME; - info->edev->dev.parent = &pdev->dev; ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index d9f7f1baaa03..d22c379766dd 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -706,7 +706,6 @@ static int max8997_muic_probe(struct platform_device *pdev) goto err_irq; } info->edev->name = DEV_NAME; - info->edev->dev.parent = &pdev->dev; ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { diff --git a/drivers/extcon/extcon-palmas.c b/drivers/extcon/extcon-palmas.c index 5efa96c00196..230e1220ce48 100644 --- a/drivers/extcon/extcon-palmas.c +++ b/drivers/extcon/extcon-palmas.c @@ -194,7 +194,6 @@ static int palmas_usb_probe(struct platform_device *pdev) 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); -- cgit v1.2.3 From d0540f91cf74fab90e1143d8d40da8a5b5fabc8a Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 21 May 2014 08:52:47 +0200 Subject: mfd: max77693: Remove unnecessary wrapper functions This patch removes wrapper functions used to access regmap, and make driver using regmap_*() functions instead. Signed-off-by: Robert Baldyga Reviewed-by: Krzysztof Kozlowski Acked-by: Mark Brown Acked-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/extcon/extcon-max77693.c | 32 ++++++++++----------- drivers/mfd/max77693-irq.c | 50 +++++++++++++++++++------------- drivers/mfd/max77693.c | 56 ++---------------------------------- drivers/regulator/max77693.c | 12 ++++---- include/linux/mfd/max77693-private.h | 8 ------ 5 files changed, 54 insertions(+), 104 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 2c7c3e191591..ba84a6e77e03 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -255,10 +255,10 @@ static int max77693_muic_set_debounce_time(struct max77693_muic_info *info, case ADC_DEBOUNCE_TIME_10MS: case ADC_DEBOUNCE_TIME_25MS: case ADC_DEBOUNCE_TIME_38_62MS: - ret = max77693_update_reg(info->max77693->regmap_muic, + ret = regmap_update_bits(info->max77693->regmap_muic, MAX77693_MUIC_REG_CTRL3, - time << CONTROL3_ADCDBSET_SHIFT, - CONTROL3_ADCDBSET_MASK); + CONTROL3_ADCDBSET_MASK, + time << CONTROL3_ADCDBSET_SHIFT); if (ret) { dev_err(info->dev, "failed to set ADC debounce time\n"); return ret; @@ -286,15 +286,15 @@ static int max77693_muic_set_path(struct max77693_muic_info *info, u8 val, bool attached) { int ret = 0; - u8 ctrl1, ctrl2 = 0; + unsigned int ctrl1, ctrl2 = 0; if (attached) ctrl1 = val; else ctrl1 = CONTROL1_SW_OPEN; - ret = max77693_update_reg(info->max77693->regmap_muic, - MAX77693_MUIC_REG_CTRL1, ctrl1, COMP_SW_MASK); + ret = regmap_update_bits(info->max77693->regmap_muic, + MAX77693_MUIC_REG_CTRL1, COMP_SW_MASK, ctrl1); if (ret < 0) { dev_err(info->dev, "failed to update MUIC register\n"); return ret; @@ -305,9 +305,9 @@ static int max77693_muic_set_path(struct max77693_muic_info *info, else ctrl2 |= CONTROL2_LOWPWR_MASK; /* LowPwr=1, CPEn=0 */ - ret = max77693_update_reg(info->max77693->regmap_muic, - MAX77693_MUIC_REG_CTRL2, ctrl2, - CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK); + ret = regmap_update_bits(info->max77693->regmap_muic, + MAX77693_MUIC_REG_CTRL2, + CONTROL2_LOWPWR_MASK | CONTROL2_CPEN_MASK, ctrl2); if (ret < 0) { dev_err(info->dev, "failed to update MUIC register\n"); return ret; @@ -969,8 +969,8 @@ static void max77693_muic_irq_work(struct work_struct *work) if (info->irq == muic_irqs[i].virq) irq_type = muic_irqs[i].irq; - ret = max77693_bulk_read(info->max77693->regmap_muic, - MAX77693_MUIC_REG_STATUS1, 2, info->status); + ret = regmap_bulk_read(info->max77693->regmap_muic, + MAX77693_MUIC_REG_STATUS1, info->status, 2); if (ret) { dev_err(info->dev, "failed to read MUIC register\n"); mutex_unlock(&info->mutex); @@ -1042,8 +1042,8 @@ static int max77693_muic_detect_accessory(struct max77693_muic_info *info) mutex_lock(&info->mutex); /* Read STATUSx register to detect accessory */ - ret = max77693_bulk_read(info->max77693->regmap_muic, - MAX77693_MUIC_REG_STATUS1, 2, info->status); + ret = regmap_bulk_read(info->max77693->regmap_muic, + MAX77693_MUIC_REG_STATUS1, info->status, 2); if (ret) { dev_err(info->dev, "failed to read MUIC register\n"); mutex_unlock(&info->mutex); @@ -1095,7 +1095,7 @@ static int max77693_muic_probe(struct platform_device *pdev) int delay_jiffies; int ret; int i; - u8 id; + unsigned int id; info = devm_kzalloc(&pdev->dev, sizeof(struct max77693_muic_info), GFP_KERNEL); @@ -1204,7 +1204,7 @@ static int max77693_muic_probe(struct platform_device *pdev) enum max77693_irq_source irq_src = MAX77693_IRQ_GROUP_NR; - max77693_write_reg(info->max77693->regmap_muic, + regmap_write(info->max77693->regmap_muic, init_data[i].addr, init_data[i].data); @@ -1262,7 +1262,7 @@ static int max77693_muic_probe(struct platform_device *pdev) max77693_muic_set_path(info, info->path_uart, true); /* Check revision number of MUIC device*/ - ret = max77693_read_reg(info->max77693->regmap_muic, + ret = regmap_read(info->max77693->regmap_muic, MAX77693_MUIC_REG_ID, &id); if (ret < 0) { dev_err(&pdev->dev, "failed to read revision number\n"); diff --git a/drivers/mfd/max77693-irq.c b/drivers/mfd/max77693-irq.c index 66b58fe77094..7d8f99f94f27 100644 --- a/drivers/mfd/max77693-irq.c +++ b/drivers/mfd/max77693-irq.c @@ -30,8 +30,9 @@ #include #include #include +#include -static const u8 max77693_mask_reg[] = { +static const unsigned int max77693_mask_reg[] = { [LED_INT] = MAX77693_LED_REG_FLASH_INT_MASK, [TOPSYS_INT] = MAX77693_PMIC_REG_TOPSYS_INT_MASK, [CHG_INT] = MAX77693_CHG_REG_CHG_INT_MASK, @@ -118,7 +119,7 @@ static void max77693_irq_sync_unlock(struct irq_data *data) continue; max77693->irq_masks_cache[i] = max77693->irq_masks_cur[i]; - max77693_write_reg(map, max77693_mask_reg[i], + regmap_write(map, max77693_mask_reg[i], max77693->irq_masks_cur[i]); } @@ -178,11 +179,11 @@ static irqreturn_t max77693_irq_thread(int irq, void *data) { struct max77693_dev *max77693 = data; u8 irq_reg[MAX77693_IRQ_GROUP_NR] = {}; - u8 irq_src; + unsigned int irq_src; int ret; int i, cur_irq; - ret = max77693_read_reg(max77693->regmap, MAX77693_PMIC_REG_INTSRC, + ret = regmap_read(max77693->regmap, MAX77693_PMIC_REG_INTSRC, &irq_src); if (ret < 0) { dev_err(max77693->dev, "Failed to read interrupt source: %d\n", @@ -190,25 +191,34 @@ static irqreturn_t max77693_irq_thread(int irq, void *data) return IRQ_NONE; } - if (irq_src & MAX77693_IRQSRC_CHG) + if (irq_src & MAX77693_IRQSRC_CHG) { /* CHG_INT */ - ret = max77693_read_reg(max77693->regmap, MAX77693_CHG_REG_CHG_INT, - &irq_reg[CHG_INT]); + unsigned int data; + ret = regmap_read(max77693->regmap, + MAX77693_CHG_REG_CHG_INT, &data); + irq_reg[CHG_INT] = data; + } - if (irq_src & MAX77693_IRQSRC_TOP) + if (irq_src & MAX77693_IRQSRC_TOP) { /* TOPSYS_INT */ - ret = max77693_read_reg(max77693->regmap, - MAX77693_PMIC_REG_TOPSYS_INT, &irq_reg[TOPSYS_INT]); + unsigned int data; + ret = regmap_read(max77693->regmap, + MAX77693_PMIC_REG_TOPSYS_INT, &data); + irq_reg[TOPSYS_INT] = data; + } - if (irq_src & MAX77693_IRQSRC_FLASH) + if (irq_src & MAX77693_IRQSRC_FLASH) { /* LED_INT */ - ret = max77693_read_reg(max77693->regmap, - MAX77693_LED_REG_FLASH_INT, &irq_reg[LED_INT]); + unsigned int data; + ret = regmap_read(max77693->regmap, + MAX77693_LED_REG_FLASH_INT, &data); + irq_reg[LED_INT] = data; + } if (irq_src & MAX77693_IRQSRC_MUIC) /* MUIC INT1 ~ INT3 */ - max77693_bulk_read(max77693->regmap_muic, MAX77693_MUIC_REG_INT1, - MAX77693_NUM_IRQ_MUIC_REGS, &irq_reg[MUIC_INT1]); + regmap_bulk_read(max77693->regmap_muic, MAX77693_MUIC_REG_INT1, + &irq_reg[MUIC_INT1], MAX77693_NUM_IRQ_MUIC_REGS); /* Apply masking */ for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { @@ -263,7 +273,7 @@ int max77693_irq_init(struct max77693_dev *max77693) struct irq_domain *domain; int i; int ret = 0; - u8 intsrc_mask; + unsigned int intsrc_mask; mutex_init(&max77693->irqlock); @@ -286,9 +296,9 @@ int max77693_irq_init(struct max77693_dev *max77693) if (max77693_mask_reg[i] == MAX77693_REG_INVALID) continue; if (i >= MUIC_INT1 && i <= MUIC_INT3) - max77693_write_reg(map, max77693_mask_reg[i], 0x00); + regmap_write(map, max77693_mask_reg[i], 0x00); else - max77693_write_reg(map, max77693_mask_reg[i], 0xff); + regmap_write(map, max77693_mask_reg[i], 0xff); } domain = irq_domain_add_linear(NULL, MAX77693_IRQ_NR, @@ -301,7 +311,7 @@ int max77693_irq_init(struct max77693_dev *max77693) max77693->irq_domain = domain; /* Unmask max77693 interrupt */ - ret = max77693_read_reg(max77693->regmap, + ret = regmap_read(max77693->regmap, MAX77693_PMIC_REG_INTSRC_MASK, &intsrc_mask); if (ret < 0) { dev_err(max77693->dev, "fail to read PMIC register\n"); @@ -311,7 +321,7 @@ int max77693_irq_init(struct max77693_dev *max77693) intsrc_mask &= ~(MAX77693_IRQSRC_CHG); intsrc_mask &= ~(MAX77693_IRQSRC_FLASH); intsrc_mask &= ~(MAX77693_IRQSRC_MUIC); - ret = max77693_write_reg(max77693->regmap, + ret = regmap_write(max77693->regmap, MAX77693_PMIC_REG_INTSRC_MASK, intsrc_mask); if (ret < 0) { dev_err(max77693->dev, "fail to write PMIC register\n"); diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c index 7e05428c756d..a0308336adaf 100644 --- a/drivers/mfd/max77693.c +++ b/drivers/mfd/max77693.c @@ -49,58 +49,6 @@ static const struct mfd_cell max77693_devs[] = { { .name = "max77693-haptic", }, }; -int max77693_read_reg(struct regmap *map, u8 reg, u8 *dest) -{ - unsigned int val; - int ret; - - ret = regmap_read(map, reg, &val); - *dest = val; - - return ret; -} -EXPORT_SYMBOL_GPL(max77693_read_reg); - -int max77693_bulk_read(struct regmap *map, u8 reg, int count, u8 *buf) -{ - int ret; - - ret = regmap_bulk_read(map, reg, buf, count); - - return ret; -} -EXPORT_SYMBOL_GPL(max77693_bulk_read); - -int max77693_write_reg(struct regmap *map, u8 reg, u8 value) -{ - int ret; - - ret = regmap_write(map, reg, value); - - return ret; -} -EXPORT_SYMBOL_GPL(max77693_write_reg); - -int max77693_bulk_write(struct regmap *map, u8 reg, int count, u8 *buf) -{ - int ret; - - ret = regmap_bulk_write(map, reg, buf, count); - - return ret; -} -EXPORT_SYMBOL_GPL(max77693_bulk_write); - -int max77693_update_reg(struct regmap *map, u8 reg, u8 val, u8 mask) -{ - int ret; - - ret = regmap_update_bits(map, reg, mask, val); - - return ret; -} -EXPORT_SYMBOL_GPL(max77693_update_reg); - static const struct regmap_config max77693_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -117,7 +65,7 @@ static int max77693_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct max77693_dev *max77693; - u8 reg_data; + unsigned int reg_data; int ret = 0; max77693 = devm_kzalloc(&i2c->dev, @@ -139,7 +87,7 @@ static int max77693_i2c_probe(struct i2c_client *i2c, return ret; } - ret = max77693_read_reg(max77693->regmap, MAX77693_PMIC_REG_PMIC_ID2, + ret = regmap_read(max77693->regmap, MAX77693_PMIC_REG_PMIC_ID2, ®_data); if (ret < 0) { dev_err(max77693->dev, "device not found on this channel\n"); diff --git a/drivers/regulator/max77693.c b/drivers/regulator/max77693.c index 653a58b49cdf..c67ff05fc1dd 100644 --- a/drivers/regulator/max77693.c +++ b/drivers/regulator/max77693.c @@ -31,6 +31,7 @@ #include #include #include +#include #define CHGIN_ILIM_STEP_20mA 20000 @@ -39,9 +40,9 @@ static int max77693_chg_is_enabled(struct regulator_dev *rdev) { int ret; - u8 val; + unsigned int val; - ret = max77693_read_reg(rdev->regmap, rdev->desc->enable_reg, &val); + ret = regmap_read(rdev->regmap, rdev->desc->enable_reg, &val); if (ret) return ret; @@ -57,12 +58,11 @@ static int max77693_chg_get_current_limit(struct regulator_dev *rdev) { unsigned int chg_min_uA = rdev->constraints->min_uA; unsigned int chg_max_uA = rdev->constraints->max_uA; - u8 reg, sel; + unsigned int reg, sel; unsigned int val; int ret; - ret = max77693_read_reg(rdev->regmap, - MAX77693_CHG_REG_CHG_CNFG_09, ®); + ret = regmap_read(rdev->regmap, MAX77693_CHG_REG_CHG_CNFG_09, ®); if (ret < 0) return ret; @@ -96,7 +96,7 @@ static int max77693_chg_set_current_limit(struct regulator_dev *rdev, /* the first four codes for charger current are all 60mA */ sel += 3; - return max77693_write_reg(rdev->regmap, + return regmap_write(rdev->regmap, MAX77693_CHG_REG_CHG_CNFG_09, sel); } /* end of CHARGER regulator ops */ diff --git a/include/linux/mfd/max77693-private.h b/include/linux/mfd/max77693-private.h index 3e050b933dd0..80ec31d561c4 100644 --- a/include/linux/mfd/max77693-private.h +++ b/include/linux/mfd/max77693-private.h @@ -332,14 +332,6 @@ enum max77693_types { TYPE_MAX77693, }; -extern int max77693_read_reg(struct regmap *map, u8 reg, u8 *dest); -extern int max77693_bulk_read(struct regmap *map, u8 reg, int count, - u8 *buf); -extern int max77693_write_reg(struct regmap *map, u8 reg, u8 value); -extern int max77693_bulk_write(struct regmap *map, u8 reg, int count, - u8 *buf); -extern int max77693_update_reg(struct regmap *map, u8 reg, u8 val, u8 mask); - extern int max77693_irq_init(struct max77693_dev *max77686); extern void max77693_irq_exit(struct max77693_dev *max77686); extern int max77693_irq_resume(struct max77693_dev *max77686); -- cgit v1.2.3 From 342d669c1ee421323f552a62729d3a3d0065093c Mon Sep 17 00:00:00 2001 From: Robert Baldyga Date: Wed, 21 May 2014 08:52:48 +0200 Subject: mfd: max77693: Handle IRQs using regmap This patch modifies mfd driver to use regmap for handling interrupts. It allows to simplify irq handling process. This modifications needed to make small changes in function drivers, which use interrupts. Signed-off-by: Robert Baldyga Reviewed-by: Krzysztof Kozlowski Acked-by: Chanwoo Choi Signed-off-by: Lee Jones --- drivers/extcon/extcon-max77693.c | 3 +- drivers/mfd/Kconfig | 1 + drivers/mfd/Makefile | 2 +- drivers/mfd/max77693-irq.c | 346 ----------------------------------- drivers/mfd/max77693.c | 158 ++++++++++++++-- include/linux/mfd/max77693-private.h | 46 ++++- 6 files changed, 195 insertions(+), 361 deletions(-) delete mode 100644 drivers/mfd/max77693-irq.c diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index ba84a6e77e03..c7278b1649da 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1154,7 +1154,8 @@ static int max77693_muic_probe(struct platform_device *pdev) struct max77693_muic_irq *muic_irq = &muic_irqs[i]; unsigned int virq = 0; - virq = irq_create_mapping(max77693->irq_domain, muic_irq->irq); + virq = regmap_irq_get_virq(max77693->irq_data_muic, + muic_irq->irq); if (!virq) { ret = -EINVAL; goto err_irq; diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index ee8204cc31e9..2feac14d1085 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -384,6 +384,7 @@ config MFD_MAX77693 depends on I2C=y select MFD_CORE select REGMAP_I2C + select REGMAP_IRQ help Say yes here to add support for Maxim Semiconductor MAX77693. This is a companion Power Management IC with Flash, Haptic, Charger, diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 8afedba535c7..8c6e7bba4660 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -116,7 +116,7 @@ obj-$(CONFIG_MFD_DA9063) += da9063.o obj-$(CONFIG_MFD_MAX14577) += max14577.o obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o -obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o +obj-$(CONFIG_MFD_MAX77693) += max77693.o obj-$(CONFIG_MFD_MAX8907) += max8907.o max8925-objs := max8925-core.o max8925-i2c.o obj-$(CONFIG_MFD_MAX8925) += max8925.o diff --git a/drivers/mfd/max77693-irq.c b/drivers/mfd/max77693-irq.c deleted file mode 100644 index 7d8f99f94f27..000000000000 --- a/drivers/mfd/max77693-irq.c +++ /dev/null @@ -1,346 +0,0 @@ -/* - * max77693-irq.c - Interrupt controller support for MAX77693 - * - * Copyright (C) 2012 Samsung Electronics Co.Ltd - * SangYoung Son - * - * This program is not provided / owned by Maxim Integrated Products. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * This driver is based on max8997-irq.c - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -static const unsigned int max77693_mask_reg[] = { - [LED_INT] = MAX77693_LED_REG_FLASH_INT_MASK, - [TOPSYS_INT] = MAX77693_PMIC_REG_TOPSYS_INT_MASK, - [CHG_INT] = MAX77693_CHG_REG_CHG_INT_MASK, - [MUIC_INT1] = MAX77693_MUIC_REG_INTMASK1, - [MUIC_INT2] = MAX77693_MUIC_REG_INTMASK2, - [MUIC_INT3] = MAX77693_MUIC_REG_INTMASK3, -}; - -static struct regmap *max77693_get_regmap(struct max77693_dev *max77693, - enum max77693_irq_source src) -{ - switch (src) { - case LED_INT ... CHG_INT: - return max77693->regmap; - case MUIC_INT1 ... MUIC_INT3: - return max77693->regmap_muic; - default: - return ERR_PTR(-EINVAL); - } -} - -struct max77693_irq_data { - int mask; - enum max77693_irq_source group; -}; - -#define DECLARE_IRQ(idx, _group, _mask) \ - [(idx)] = { .group = (_group), .mask = (_mask) } -static const struct max77693_irq_data max77693_irqs[] = { - DECLARE_IRQ(MAX77693_LED_IRQ_FLED2_OPEN, LED_INT, 1 << 0), - DECLARE_IRQ(MAX77693_LED_IRQ_FLED2_SHORT, LED_INT, 1 << 1), - DECLARE_IRQ(MAX77693_LED_IRQ_FLED1_OPEN, LED_INT, 1 << 2), - DECLARE_IRQ(MAX77693_LED_IRQ_FLED1_SHORT, LED_INT, 1 << 3), - DECLARE_IRQ(MAX77693_LED_IRQ_MAX_FLASH, LED_INT, 1 << 4), - - DECLARE_IRQ(MAX77693_TOPSYS_IRQ_T120C_INT, TOPSYS_INT, 1 << 0), - DECLARE_IRQ(MAX77693_TOPSYS_IRQ_T140C_INT, TOPSYS_INT, 1 << 1), - DECLARE_IRQ(MAX77693_TOPSYS_IRQ_LOWSYS_INT, TOPSYS_INT, 1 << 3), - - DECLARE_IRQ(MAX77693_CHG_IRQ_BYP_I, CHG_INT, 1 << 0), - DECLARE_IRQ(MAX77693_CHG_IRQ_THM_I, CHG_INT, 1 << 2), - DECLARE_IRQ(MAX77693_CHG_IRQ_BAT_I, CHG_INT, 1 << 3), - DECLARE_IRQ(MAX77693_CHG_IRQ_CHG_I, CHG_INT, 1 << 4), - DECLARE_IRQ(MAX77693_CHG_IRQ_CHGIN_I, CHG_INT, 1 << 6), - - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADC, MUIC_INT1, 1 << 0), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADC_LOW, MUIC_INT1, 1 << 1), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADC_ERR, MUIC_INT1, 1 << 2), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT1_ADC1K, MUIC_INT1, 1 << 3), - - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_CHGTYP, MUIC_INT2, 1 << 0), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_CHGDETREUN, MUIC_INT2, 1 << 1), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_DCDTMR, MUIC_INT2, 1 << 2), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_DXOVP, MUIC_INT2, 1 << 3), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_VBVOLT, MUIC_INT2, 1 << 4), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT2_VIDRM, MUIC_INT2, 1 << 5), - - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_EOC, MUIC_INT3, 1 << 0), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_CGMBC, MUIC_INT3, 1 << 1), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_OVP, MUIC_INT3, 1 << 2), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_MBCCHG_ERR, MUIC_INT3, 1 << 3), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_CHG_ENABLED, MUIC_INT3, 1 << 4), - DECLARE_IRQ(MAX77693_MUIC_IRQ_INT3_BAT_DET, MUIC_INT3, 1 << 5), -}; - -static void max77693_irq_lock(struct irq_data *data) -{ - struct max77693_dev *max77693 = irq_get_chip_data(data->irq); - - mutex_lock(&max77693->irqlock); -} - -static void max77693_irq_sync_unlock(struct irq_data *data) -{ - struct max77693_dev *max77693 = irq_get_chip_data(data->irq); - int i; - - for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { - u8 mask_reg = max77693_mask_reg[i]; - struct regmap *map = max77693_get_regmap(max77693, i); - - if (mask_reg == MAX77693_REG_INVALID || - IS_ERR_OR_NULL(map)) - continue; - max77693->irq_masks_cache[i] = max77693->irq_masks_cur[i]; - - regmap_write(map, max77693_mask_reg[i], - max77693->irq_masks_cur[i]); - } - - mutex_unlock(&max77693->irqlock); -} - -static const inline struct max77693_irq_data * -irq_to_max77693_irq(struct max77693_dev *max77693, int irq) -{ - struct irq_data *data = irq_get_irq_data(irq); - return &max77693_irqs[data->hwirq]; -} - -static void max77693_irq_mask(struct irq_data *data) -{ - struct max77693_dev *max77693 = irq_get_chip_data(data->irq); - const struct max77693_irq_data *irq_data = - irq_to_max77693_irq(max77693, data->irq); - - if (irq_data->group >= MAX77693_IRQ_GROUP_NR) - return; - - if (irq_data->group >= MUIC_INT1 && irq_data->group <= MUIC_INT3) - max77693->irq_masks_cur[irq_data->group] &= ~irq_data->mask; - else - max77693->irq_masks_cur[irq_data->group] |= irq_data->mask; -} - -static void max77693_irq_unmask(struct irq_data *data) -{ - struct max77693_dev *max77693 = irq_get_chip_data(data->irq); - const struct max77693_irq_data *irq_data = - irq_to_max77693_irq(max77693, data->irq); - - if (irq_data->group >= MAX77693_IRQ_GROUP_NR) - return; - - if (irq_data->group >= MUIC_INT1 && irq_data->group <= MUIC_INT3) - max77693->irq_masks_cur[irq_data->group] |= irq_data->mask; - else - max77693->irq_masks_cur[irq_data->group] &= ~irq_data->mask; -} - -static struct irq_chip max77693_irq_chip = { - .name = "max77693", - .irq_bus_lock = max77693_irq_lock, - .irq_bus_sync_unlock = max77693_irq_sync_unlock, - .irq_mask = max77693_irq_mask, - .irq_unmask = max77693_irq_unmask, -}; - -#define MAX77693_IRQSRC_CHG (1 << 0) -#define MAX77693_IRQSRC_TOP (1 << 1) -#define MAX77693_IRQSRC_FLASH (1 << 2) -#define MAX77693_IRQSRC_MUIC (1 << 3) -static irqreturn_t max77693_irq_thread(int irq, void *data) -{ - struct max77693_dev *max77693 = data; - u8 irq_reg[MAX77693_IRQ_GROUP_NR] = {}; - unsigned int irq_src; - int ret; - int i, cur_irq; - - ret = regmap_read(max77693->regmap, MAX77693_PMIC_REG_INTSRC, - &irq_src); - if (ret < 0) { - dev_err(max77693->dev, "Failed to read interrupt source: %d\n", - ret); - return IRQ_NONE; - } - - if (irq_src & MAX77693_IRQSRC_CHG) { - /* CHG_INT */ - unsigned int data; - ret = regmap_read(max77693->regmap, - MAX77693_CHG_REG_CHG_INT, &data); - irq_reg[CHG_INT] = data; - } - - if (irq_src & MAX77693_IRQSRC_TOP) { - /* TOPSYS_INT */ - unsigned int data; - ret = regmap_read(max77693->regmap, - MAX77693_PMIC_REG_TOPSYS_INT, &data); - irq_reg[TOPSYS_INT] = data; - } - - if (irq_src & MAX77693_IRQSRC_FLASH) { - /* LED_INT */ - unsigned int data; - ret = regmap_read(max77693->regmap, - MAX77693_LED_REG_FLASH_INT, &data); - irq_reg[LED_INT] = data; - } - - if (irq_src & MAX77693_IRQSRC_MUIC) - /* MUIC INT1 ~ INT3 */ - regmap_bulk_read(max77693->regmap_muic, MAX77693_MUIC_REG_INT1, - &irq_reg[MUIC_INT1], MAX77693_NUM_IRQ_MUIC_REGS); - - /* Apply masking */ - for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { - if (i >= MUIC_INT1 && i <= MUIC_INT3) - irq_reg[i] &= max77693->irq_masks_cur[i]; - else - irq_reg[i] &= ~max77693->irq_masks_cur[i]; - } - - /* Report */ - for (i = 0; i < MAX77693_IRQ_NR; i++) { - if (irq_reg[max77693_irqs[i].group] & max77693_irqs[i].mask) { - cur_irq = irq_find_mapping(max77693->irq_domain, i); - if (cur_irq) - handle_nested_irq(cur_irq); - } - } - - return IRQ_HANDLED; -} - -int max77693_irq_resume(struct max77693_dev *max77693) -{ - if (max77693->irq) - max77693_irq_thread(0, max77693); - - return 0; -} - -static int max77693_irq_domain_map(struct irq_domain *d, unsigned int irq, - irq_hw_number_t hw) -{ - struct max77693_dev *max77693 = d->host_data; - - irq_set_chip_data(irq, max77693); - irq_set_chip_and_handler(irq, &max77693_irq_chip, handle_edge_irq); - irq_set_nested_thread(irq, 1); -#ifdef CONFIG_ARM - set_irq_flags(irq, IRQF_VALID); -#else - irq_set_noprobe(irq); -#endif - return 0; -} - -static struct irq_domain_ops max77693_irq_domain_ops = { - .map = max77693_irq_domain_map, -}; - -int max77693_irq_init(struct max77693_dev *max77693) -{ - struct irq_domain *domain; - int i; - int ret = 0; - unsigned int intsrc_mask; - - mutex_init(&max77693->irqlock); - - /* Mask individual interrupt sources */ - for (i = 0; i < MAX77693_IRQ_GROUP_NR; i++) { - struct regmap *map; - /* MUIC IRQ 0:MASK 1:NOT MASK */ - /* Other IRQ 1:MASK 0:NOT MASK */ - if (i >= MUIC_INT1 && i <= MUIC_INT3) { - max77693->irq_masks_cur[i] = 0x00; - max77693->irq_masks_cache[i] = 0x00; - } else { - max77693->irq_masks_cur[i] = 0xff; - max77693->irq_masks_cache[i] = 0xff; - } - map = max77693_get_regmap(max77693, i); - - if (IS_ERR_OR_NULL(map)) - continue; - if (max77693_mask_reg[i] == MAX77693_REG_INVALID) - continue; - if (i >= MUIC_INT1 && i <= MUIC_INT3) - regmap_write(map, max77693_mask_reg[i], 0x00); - else - regmap_write(map, max77693_mask_reg[i], 0xff); - } - - domain = irq_domain_add_linear(NULL, MAX77693_IRQ_NR, - &max77693_irq_domain_ops, max77693); - if (!domain) { - dev_err(max77693->dev, "could not create irq domain\n"); - ret = -ENODEV; - goto err_irq; - } - max77693->irq_domain = domain; - - /* Unmask max77693 interrupt */ - ret = regmap_read(max77693->regmap, - MAX77693_PMIC_REG_INTSRC_MASK, &intsrc_mask); - if (ret < 0) { - dev_err(max77693->dev, "fail to read PMIC register\n"); - goto err_irq; - } - - intsrc_mask &= ~(MAX77693_IRQSRC_CHG); - intsrc_mask &= ~(MAX77693_IRQSRC_FLASH); - intsrc_mask &= ~(MAX77693_IRQSRC_MUIC); - ret = regmap_write(max77693->regmap, - MAX77693_PMIC_REG_INTSRC_MASK, intsrc_mask); - if (ret < 0) { - dev_err(max77693->dev, "fail to write PMIC register\n"); - goto err_irq; - } - - ret = request_threaded_irq(max77693->irq, NULL, max77693_irq_thread, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - "max77693-irq", max77693); - if (ret) - dev_err(max77693->dev, "Failed to request IRQ %d: %d\n", - max77693->irq, ret); - -err_irq: - return ret; -} - -void max77693_irq_exit(struct max77693_dev *max77693) -{ - if (max77693->irq) - free_irq(max77693->irq, max77693); -} diff --git a/drivers/mfd/max77693.c b/drivers/mfd/max77693.c index a0308336adaf..249c139ef04a 100644 --- a/drivers/mfd/max77693.c +++ b/drivers/mfd/max77693.c @@ -55,12 +55,95 @@ static const struct regmap_config max77693_regmap_config = { .max_register = MAX77693_PMIC_REG_END, }; +static const struct regmap_irq max77693_led_irqs[] = { + { .mask = LED_IRQ_FLED2_OPEN, }, + { .mask = LED_IRQ_FLED2_SHORT, }, + { .mask = LED_IRQ_FLED1_OPEN, }, + { .mask = LED_IRQ_FLED1_SHORT, }, + { .mask = LED_IRQ_MAX_FLASH, }, +}; + +static const struct regmap_irq_chip max77693_led_irq_chip = { + .name = "max77693-led", + .status_base = MAX77693_LED_REG_FLASH_INT, + .mask_base = MAX77693_LED_REG_FLASH_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77693_led_irqs, + .num_irqs = ARRAY_SIZE(max77693_led_irqs), +}; + +static const struct regmap_irq max77693_topsys_irqs[] = { + { .mask = TOPSYS_IRQ_T120C_INT, }, + { .mask = TOPSYS_IRQ_T140C_INT, }, + { .mask = TOPSYS_IRQ_LOWSYS_INT, }, +}; + +static const struct regmap_irq_chip max77693_topsys_irq_chip = { + .name = "max77693-topsys", + .status_base = MAX77693_PMIC_REG_TOPSYS_INT, + .mask_base = MAX77693_PMIC_REG_TOPSYS_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77693_topsys_irqs, + .num_irqs = ARRAY_SIZE(max77693_topsys_irqs), +}; + +static const struct regmap_irq max77693_charger_irqs[] = { + { .mask = CHG_IRQ_BYP_I, }, + { .mask = CHG_IRQ_THM_I, }, + { .mask = CHG_IRQ_BAT_I, }, + { .mask = CHG_IRQ_CHG_I, }, + { .mask = CHG_IRQ_CHGIN_I, }, +}; + +static const struct regmap_irq_chip max77693_charger_irq_chip = { + .name = "max77693-charger", + .status_base = MAX77693_CHG_REG_CHG_INT, + .mask_base = MAX77693_CHG_REG_CHG_INT_MASK, + .mask_invert = false, + .num_regs = 1, + .irqs = max77693_charger_irqs, + .num_irqs = ARRAY_SIZE(max77693_charger_irqs), +}; + static const struct regmap_config max77693_regmap_muic_config = { .reg_bits = 8, .val_bits = 8, .max_register = MAX77693_MUIC_REG_END, }; +static const struct regmap_irq max77693_muic_irqs[] = { + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC, }, + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC_LOW, }, + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC_ERR, }, + { .reg_offset = 0, .mask = MUIC_IRQ_INT1_ADC1K, }, + + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_CHGTYP, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_CHGDETREUN, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_DCDTMR, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_DXOVP, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_VBVOLT, }, + { .reg_offset = 1, .mask = MUIC_IRQ_INT2_VIDRM, }, + + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_EOC, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_CGMBC, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_OVP, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_MBCCHG_ERR, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_CHG_ENABLED, }, + { .reg_offset = 2, .mask = MUIC_IRQ_INT3_BAT_DET, }, +}; + +static const struct regmap_irq_chip max77693_muic_irq_chip = { + .name = "max77693-muic", + .status_base = MAX77693_MUIC_REG_INT1, + .mask_base = MAX77693_MUIC_REG_INTMASK1, + .mask_invert = true, + .num_regs = 3, + .irqs = max77693_muic_irqs, + .num_irqs = ARRAY_SIZE(max77693_muic_irqs), +}; + static int max77693_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { @@ -124,9 +207,45 @@ static int max77693_i2c_probe(struct i2c_client *i2c, goto err_regmap_muic; } - ret = max77693_irq_init(max77693); - if (ret < 0) - goto err_irq; + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_led_irq_chip, + &max77693->irq_data_led); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_regmap_muic; + } + + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_topsys_irq_chip, + &max77693->irq_data_topsys); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_irq_topsys; + } + + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_charger_irq_chip, + &max77693->irq_data_charger); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_irq_charger; + } + + ret = regmap_add_irq_chip(max77693->regmap, max77693->irq, + IRQF_ONESHOT | IRQF_SHARED | + IRQF_TRIGGER_FALLING, 0, + &max77693_muic_irq_chip, + &max77693->irq_data_muic); + if (ret) { + dev_err(max77693->dev, "failed to add irq chip: %d\n", ret); + goto err_irq_muic; + } pm_runtime_set_active(max77693->dev); @@ -138,8 +257,14 @@ static int max77693_i2c_probe(struct i2c_client *i2c, return ret; err_mfd: - max77693_irq_exit(max77693); -err_irq: + mfd_remove_devices(max77693->dev); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_muic); +err_irq_muic: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_charger); +err_irq_charger: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_topsys); +err_irq_topsys: + regmap_del_irq_chip(max77693->irq, max77693->irq_data_led); err_regmap_muic: i2c_unregister_device(max77693->haptic); err_i2c_haptic: @@ -152,7 +277,12 @@ static int max77693_i2c_remove(struct i2c_client *i2c) struct max77693_dev *max77693 = i2c_get_clientdata(i2c); mfd_remove_devices(max77693->dev); - max77693_irq_exit(max77693); + + regmap_del_irq_chip(max77693->irq, max77693->irq_data_muic); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_charger); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_topsys); + regmap_del_irq_chip(max77693->irq, max77693->irq_data_led); + i2c_unregister_device(max77693->muic); i2c_unregister_device(max77693->haptic); @@ -170,8 +300,11 @@ static int max77693_suspend(struct device *dev) struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max77693_dev *max77693 = i2c_get_clientdata(i2c); - if (device_may_wakeup(dev)) - irq_set_irq_wake(max77693->irq, 1); + if (device_may_wakeup(dev)) { + enable_irq_wake(max77693->irq); + disable_irq(max77693->irq); + } + return 0; } @@ -180,9 +313,12 @@ static int max77693_resume(struct device *dev) struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); struct max77693_dev *max77693 = i2c_get_clientdata(i2c); - if (device_may_wakeup(dev)) - irq_set_irq_wake(max77693->irq, 0); - return max77693_irq_resume(max77693); + if (device_may_wakeup(dev)) { + disable_irq_wake(max77693->irq); + enable_irq(max77693->irq); + } + + return 0; } static const struct dev_pm_ops max77693_pm = { diff --git a/include/linux/mfd/max77693-private.h b/include/linux/mfd/max77693-private.h index 80ec31d561c4..c466ff3e16b8 100644 --- a/include/linux/mfd/max77693-private.h +++ b/include/linux/mfd/max77693-private.h @@ -262,6 +262,41 @@ enum max77693_irq_source { MAX77693_IRQ_GROUP_NR, }; +#define LED_IRQ_FLED2_OPEN BIT(0) +#define LED_IRQ_FLED2_SHORT BIT(1) +#define LED_IRQ_FLED1_OPEN BIT(2) +#define LED_IRQ_FLED1_SHORT BIT(3) +#define LED_IRQ_MAX_FLASH BIT(4) + +#define TOPSYS_IRQ_T120C_INT BIT(0) +#define TOPSYS_IRQ_T140C_INT BIT(1) +#define TOPSYS_IRQ_LOWSYS_INT BIT(3) + +#define CHG_IRQ_BYP_I BIT(0) +#define CHG_IRQ_THM_I BIT(2) +#define CHG_IRQ_BAT_I BIT(3) +#define CHG_IRQ_CHG_I BIT(4) +#define CHG_IRQ_CHGIN_I BIT(6) + +#define MUIC_IRQ_INT1_ADC BIT(0) +#define MUIC_IRQ_INT1_ADC_LOW BIT(1) +#define MUIC_IRQ_INT1_ADC_ERR BIT(2) +#define MUIC_IRQ_INT1_ADC1K BIT(3) + +#define MUIC_IRQ_INT2_CHGTYP BIT(0) +#define MUIC_IRQ_INT2_CHGDETREUN BIT(1) +#define MUIC_IRQ_INT2_DCDTMR BIT(2) +#define MUIC_IRQ_INT2_DXOVP BIT(3) +#define MUIC_IRQ_INT2_VBVOLT BIT(4) +#define MUIC_IRQ_INT2_VIDRM BIT(5) + +#define MUIC_IRQ_INT3_EOC BIT(0) +#define MUIC_IRQ_INT3_CGMBC BIT(1) +#define MUIC_IRQ_INT3_OVP BIT(2) +#define MUIC_IRQ_INT3_MBCCHG_ERR BIT(3) +#define MUIC_IRQ_INT3_CHG_ENABLED BIT(4) +#define MUIC_IRQ_INT3_BAT_DET BIT(5) + enum max77693_irq { /* PMIC - FLASH */ MAX77693_LED_IRQ_FLED2_OPEN, @@ -282,6 +317,10 @@ enum max77693_irq { MAX77693_CHG_IRQ_CHG_I, MAX77693_CHG_IRQ_CHGIN_I, + MAX77693_IRQ_NR, +}; + +enum max77693_irq_muic { /* MUIC INT1 */ MAX77693_MUIC_IRQ_INT1_ADC, MAX77693_MUIC_IRQ_INT1_ADC_LOW, @@ -304,7 +343,7 @@ enum max77693_irq { MAX77693_MUIC_IRQ_INT3_CHG_ENABLED, MAX77693_MUIC_IRQ_INT3_BAT_DET, - MAX77693_IRQ_NR, + MAX77693_MUIC_IRQ_NR, }; struct max77693_dev { @@ -319,7 +358,10 @@ struct max77693_dev { struct regmap *regmap_muic; struct regmap *regmap_haptic; - struct irq_domain *irq_domain; + struct regmap_irq_chip_data *irq_data_led; + struct regmap_irq_chip_data *irq_data_topsys; + struct regmap_irq_chip_data *irq_data_charger; + struct regmap_irq_chip_data *irq_data_muic; int irq; int irq_gpio; -- cgit v1.2.3 From 16603153666d22df544ae9f9b3764fd18da28eeb Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:03:58 +0200 Subject: thunderbolt: Add initial cactus ridge NHI support Thunderbolt hotplug is supposed to be handled by the firmware. But Apple decided to implement thunderbolt at the operating system level. The firmare only initializes thunderbolt devices that are present at boot time. This driver enables hotplug of thunderbolt of non-chained thunderbolt devices on Apple systems with a cactus ridge controller. This first patch adds the Kconfig file as well the parts of the driver which talk directly to the hardware (that is pci device setup, interrupt handling and RX/TX ring management). Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/thunderbolt/Kconfig | 12 + drivers/thunderbolt/Makefile | 3 + drivers/thunderbolt/nhi.c | 630 +++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/nhi.h | 114 ++++++++ drivers/thunderbolt/nhi_regs.h | 101 +++++++ 7 files changed, 863 insertions(+) create mode 100644 drivers/thunderbolt/Kconfig create mode 100644 drivers/thunderbolt/Makefile create mode 100644 drivers/thunderbolt/nhi.c create mode 100644 drivers/thunderbolt/nhi.h create mode 100644 drivers/thunderbolt/nhi_regs.h diff --git a/drivers/Kconfig b/drivers/Kconfig index 0e87a34b6472..9b2dcc2ea663 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -176,4 +176,6 @@ source "drivers/powercap/Kconfig" source "drivers/mcb/Kconfig" +source "drivers/thunderbolt/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index f98b50d8251d..37b9ed4cd2d6 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -158,3 +158,4 @@ obj-$(CONFIG_NTB) += ntb/ obj-$(CONFIG_FMC) += fmc/ obj-$(CONFIG_POWERCAP) += powercap/ obj-$(CONFIG_MCB) += mcb/ +obj-$(CONFIG_THUNDERBOLT) += thunderbolt/ diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig new file mode 100644 index 000000000000..3a2552962d06 --- /dev/null +++ b/drivers/thunderbolt/Kconfig @@ -0,0 +1,12 @@ +menuconfig THUNDERBOLT + tristate "Thunderbolt support for Apple devices" + default no + help + Cactus Ridge Thunderbolt Controller driver + This driver is required if you want to hotplug Thunderbolt devices on + Apple hardware. + + Device chaining is currently not supported. + + To compile this driver a module, choose M here. The module will be + called thunderbolt. diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile new file mode 100644 index 000000000000..d473ab93d127 --- /dev/null +++ b/drivers/thunderbolt/Makefile @@ -0,0 +1,3 @@ +obj-${CONFIG_THUNDERBOLT} := thunderbolt.o +thunderbolt-objs := nhi.o + diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c new file mode 100644 index 000000000000..11070ff2cec7 --- /dev/null +++ b/drivers/thunderbolt/nhi.c @@ -0,0 +1,630 @@ +/* + * Thunderbolt Cactus Ridge driver - NHI driver + * + * The NHI (native host interface) is the pci device that allows us to send and + * receive frames from the thunderbolt bus. + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include +#include +#include +#include +#include + +#include "nhi.h" +#include "nhi_regs.h" + +#define RING_TYPE(ring) ((ring)->is_tx ? "TX ring" : "RX ring") + + +static int ring_interrupt_index(struct tb_ring *ring) +{ + int bit = ring->hop; + if (!ring->is_tx) + bit += ring->nhi->hop_count; + return bit; +} + +/** + * ring_interrupt_active() - activate/deactivate interrupts for a single ring + * + * ring->nhi->lock must be held. + */ +static void ring_interrupt_active(struct tb_ring *ring, bool active) +{ + int reg = REG_RING_INTERRUPT_BASE + ring_interrupt_index(ring) / 32; + int bit = ring_interrupt_index(ring) & 31; + int mask = 1 << bit; + u32 old, new; + old = ioread32(ring->nhi->iobase + reg); + if (active) + new = old | mask; + else + new = old & ~mask; + + dev_info(&ring->nhi->pdev->dev, + "%s interrupt at register %#x bit %d (%#x -> %#x)\n", + active ? "enabling" : "disabling", reg, bit, old, new); + + if (new == old) + dev_WARN(&ring->nhi->pdev->dev, + "interrupt for %s %d is already %s\n", + RING_TYPE(ring), ring->hop, + active ? "enabled" : "disabled"); + iowrite32(new, ring->nhi->iobase + reg); +} + +/** + * nhi_disable_interrupts() - disable interrupts for all rings + * + * Use only during init and shutdown. + */ +static void nhi_disable_interrupts(struct tb_nhi *nhi) +{ + int i = 0; + /* disable interrupts */ + for (i = 0; i < RING_INTERRUPT_REG_COUNT(nhi); i++) + iowrite32(0, nhi->iobase + REG_RING_INTERRUPT_BASE + 4 * i); + + /* clear interrupt status bits */ + for (i = 0; i < RING_NOTIFY_REG_COUNT(nhi); i++) + ioread32(nhi->iobase + REG_RING_NOTIFY_BASE + 4 * i); +} + +/* ring helper methods */ + +static void __iomem *ring_desc_base(struct tb_ring *ring) +{ + void __iomem *io = ring->nhi->iobase; + io += ring->is_tx ? REG_TX_RING_BASE : REG_RX_RING_BASE; + io += ring->hop * 16; + return io; +} + +static void __iomem *ring_options_base(struct tb_ring *ring) +{ + void __iomem *io = ring->nhi->iobase; + io += ring->is_tx ? REG_TX_OPTIONS_BASE : REG_RX_OPTIONS_BASE; + io += ring->hop * 32; + return io; +} + +static void ring_iowrite16desc(struct tb_ring *ring, u32 value, u32 offset) +{ + iowrite16(value, ring_desc_base(ring) + offset); +} + +static void ring_iowrite32desc(struct tb_ring *ring, u32 value, u32 offset) +{ + iowrite32(value, ring_desc_base(ring) + offset); +} + +static void ring_iowrite64desc(struct tb_ring *ring, u64 value, u32 offset) +{ + iowrite32(value, ring_desc_base(ring) + offset); + iowrite32(value >> 32, ring_desc_base(ring) + offset + 4); +} + +static void ring_iowrite32options(struct tb_ring *ring, u32 value, u32 offset) +{ + iowrite32(value, ring_options_base(ring) + offset); +} + +static bool ring_full(struct tb_ring *ring) +{ + return ((ring->head + 1) % ring->size) == ring->tail; +} + +static bool ring_empty(struct tb_ring *ring) +{ + return ring->head == ring->tail; +} + +/** + * ring_write_descriptors() - post frames from ring->queue to the controller + * + * ring->lock is held. + */ +static void ring_write_descriptors(struct tb_ring *ring) +{ + struct ring_frame *frame, *n; + struct ring_desc *descriptor; + list_for_each_entry_safe(frame, n, &ring->queue, list) { + if (ring_full(ring)) + break; + list_move_tail(&frame->list, &ring->in_flight); + descriptor = &ring->descriptors[ring->head]; + descriptor->phys = frame->buffer_phy; + descriptor->time = 0; + descriptor->flags = RING_DESC_POSTED | RING_DESC_INTERRUPT; + if (ring->is_tx) { + descriptor->length = frame->size; + descriptor->eof = frame->eof; + descriptor->sof = frame->sof; + } + ring->head = (ring->head + 1) % ring->size; + ring_iowrite16desc(ring, ring->head, ring->is_tx ? 10 : 8); + } +} + +/** + * ring_work() - progress completed frames + * + * If the ring is shutting down then all frames are marked as canceled and + * their callbacks are invoked. + * + * Otherwise we collect all completed frame from the ring buffer, write new + * frame to the ring buffer and invoke the callbacks for the completed frames. + */ +static void ring_work(struct work_struct *work) +{ + struct tb_ring *ring = container_of(work, typeof(*ring), work); + struct ring_frame *frame; + bool canceled = false; + LIST_HEAD(done); + mutex_lock(&ring->lock); + + if (!ring->running) { + /* Move all frames to done and mark them as canceled. */ + list_splice_tail_init(&ring->in_flight, &done); + list_splice_tail_init(&ring->queue, &done); + canceled = true; + goto invoke_callback; + } + + while (!ring_empty(ring)) { + if (!(ring->descriptors[ring->tail].flags + & RING_DESC_COMPLETED)) + break; + frame = list_first_entry(&ring->in_flight, typeof(*frame), + list); + list_move_tail(&frame->list, &done); + if (!ring->is_tx) { + frame->size = ring->descriptors[ring->tail].length; + frame->eof = ring->descriptors[ring->tail].eof; + frame->sof = ring->descriptors[ring->tail].sof; + frame->flags = ring->descriptors[ring->tail].flags; + if (frame->sof != 0) + dev_WARN(&ring->nhi->pdev->dev, + "%s %d got unexpected SOF: %#x\n", + RING_TYPE(ring), ring->hop, + frame->sof); + /* + * known flags: + * raw not enabled, interupt not set: 0x2=0010 + * raw enabled: 0xa=1010 + * raw not enabled: 0xb=1011 + * partial frame (>MAX_FRAME_SIZE): 0xe=1110 + */ + if (frame->flags != 0xa) + dev_WARN(&ring->nhi->pdev->dev, + "%s %d got unexpected flags: %#x\n", + RING_TYPE(ring), ring->hop, + frame->flags); + } + ring->tail = (ring->tail + 1) % ring->size; + } + ring_write_descriptors(ring); + +invoke_callback: + mutex_unlock(&ring->lock); /* allow callbacks to schedule new work */ + while (!list_empty(&done)) { + frame = list_first_entry(&done, typeof(*frame), list); + /* + * The callback may reenqueue or delete frame. + * Do not hold on to it. + */ + list_del_init(&frame->list); + frame->callback(ring, frame, canceled); + } +} + +int __ring_enqueue(struct tb_ring *ring, struct ring_frame *frame) +{ + int ret = 0; + mutex_lock(&ring->lock); + if (ring->running) { + list_add_tail(&frame->list, &ring->queue); + ring_write_descriptors(ring); + } else { + ret = -ESHUTDOWN; + } + mutex_unlock(&ring->lock); + return ret; +} + +static struct tb_ring *ring_alloc(struct tb_nhi *nhi, u32 hop, int size, + bool transmit) +{ + struct tb_ring *ring = NULL; + dev_info(&nhi->pdev->dev, "allocating %s ring %d of size %d\n", + transmit ? "TX" : "RX", hop, size); + + mutex_lock(&nhi->lock); + if (hop >= nhi->hop_count) { + dev_WARN(&nhi->pdev->dev, "invalid hop: %d\n", hop); + goto err; + } + if (transmit && nhi->tx_rings[hop]) { + dev_WARN(&nhi->pdev->dev, "TX hop %d already allocated\n", hop); + goto err; + } else if (!transmit && nhi->rx_rings[hop]) { + dev_WARN(&nhi->pdev->dev, "RX hop %d already allocated\n", hop); + goto err; + } + ring = kzalloc(sizeof(*ring), GFP_KERNEL); + if (!ring) + goto err; + + mutex_init(&ring->lock); + INIT_LIST_HEAD(&ring->queue); + INIT_LIST_HEAD(&ring->in_flight); + INIT_WORK(&ring->work, ring_work); + + ring->nhi = nhi; + ring->hop = hop; + ring->is_tx = transmit; + ring->size = size; + ring->head = 0; + ring->tail = 0; + ring->running = false; + ring->descriptors = dma_alloc_coherent(&ring->nhi->pdev->dev, + size * sizeof(*ring->descriptors), + &ring->descriptors_dma, GFP_KERNEL | __GFP_ZERO); + if (!ring->descriptors) + goto err; + + if (transmit) + nhi->tx_rings[hop] = ring; + else + nhi->rx_rings[hop] = ring; + mutex_unlock(&nhi->lock); + return ring; + +err: + if (ring) + mutex_destroy(&ring->lock); + kfree(ring); + mutex_unlock(&nhi->lock); + return NULL; +} + +struct tb_ring *ring_alloc_tx(struct tb_nhi *nhi, int hop, int size) +{ + return ring_alloc(nhi, hop, size, true); +} + +struct tb_ring *ring_alloc_rx(struct tb_nhi *nhi, int hop, int size) +{ + return ring_alloc(nhi, hop, size, false); +} + +/** + * ring_start() - enable a ring + * + * Must not be invoked in parallel with ring_stop(). + */ +void ring_start(struct tb_ring *ring) +{ + mutex_lock(&ring->nhi->lock); + mutex_lock(&ring->lock); + if (ring->running) { + dev_WARN(&ring->nhi->pdev->dev, "ring already started\n"); + goto err; + } + dev_info(&ring->nhi->pdev->dev, "starting %s %d\n", + RING_TYPE(ring), ring->hop); + + ring_iowrite64desc(ring, ring->descriptors_dma, 0); + if (ring->is_tx) { + ring_iowrite32desc(ring, ring->size, 12); + ring_iowrite32options(ring, 0, 4); /* time releated ? */ + ring_iowrite32options(ring, + RING_FLAG_ENABLE | RING_FLAG_RAW, 0); + } else { + ring_iowrite32desc(ring, + (TB_FRAME_SIZE << 16) | ring->size, 12); + ring_iowrite32options(ring, 0xffffffff, 4); /* SOF EOF mask */ + ring_iowrite32options(ring, + RING_FLAG_ENABLE | RING_FLAG_RAW, 0); + } + ring_interrupt_active(ring, true); + ring->running = true; +err: + mutex_unlock(&ring->lock); + mutex_unlock(&ring->nhi->lock); +} + + +/** + * ring_stop() - shutdown a ring + * + * Must not be invoked from a callback. + * + * This method will disable the ring. Further calls to ring_tx/ring_rx will + * return -ESHUTDOWN until ring_stop has been called. + * + * All enqueued frames will be canceled and their callbacks will be executed + * with frame->canceled set to true (on the callback thread). This method + * returns only after all callback invocations have finished. + */ +void ring_stop(struct tb_ring *ring) +{ + mutex_lock(&ring->nhi->lock); + mutex_lock(&ring->lock); + dev_info(&ring->nhi->pdev->dev, "stopping %s %d\n", + RING_TYPE(ring), ring->hop); + if (!ring->running) { + dev_WARN(&ring->nhi->pdev->dev, "%s %d already stopped\n", + RING_TYPE(ring), ring->hop); + goto err; + } + ring_interrupt_active(ring, false); + + ring_iowrite32options(ring, 0, 0); + ring_iowrite64desc(ring, 0, 0); + ring_iowrite16desc(ring, 0, ring->is_tx ? 10 : 8); + ring_iowrite32desc(ring, 0, 12); + ring->head = 0; + ring->tail = 0; + ring->running = false; + +err: + mutex_unlock(&ring->lock); + mutex_unlock(&ring->nhi->lock); + + /* + * schedule ring->work to invoke callbacks on all remaining frames. + */ + schedule_work(&ring->work); + flush_work(&ring->work); +} + +/* + * ring_free() - free ring + * + * When this method returns all invocations of ring->callback will have + * finished. + * + * Ring must be stopped. + * + * Must NOT be called from ring_frame->callback! + */ +void ring_free(struct tb_ring *ring) +{ + mutex_lock(&ring->nhi->lock); + /* + * Dissociate the ring from the NHI. This also ensures that + * nhi_interrupt_work cannot reschedule ring->work. + */ + if (ring->is_tx) + ring->nhi->tx_rings[ring->hop] = NULL; + else + ring->nhi->rx_rings[ring->hop] = NULL; + + if (ring->running) { + dev_WARN(&ring->nhi->pdev->dev, "%s %d still running\n", + RING_TYPE(ring), ring->hop); + } + + dma_free_coherent(&ring->nhi->pdev->dev, + ring->size * sizeof(*ring->descriptors), + ring->descriptors, ring->descriptors_dma); + + ring->descriptors = 0; + ring->descriptors_dma = 0; + + + dev_info(&ring->nhi->pdev->dev, + "freeing %s %d\n", + RING_TYPE(ring), + ring->hop); + + mutex_unlock(&ring->nhi->lock); + /** + * ring->work can no longer be scheduled (it is scheduled only by + * nhi_interrupt_work and ring_stop). Wait for it to finish before + * freeing the ring. + */ + flush_work(&ring->work); + mutex_destroy(&ring->lock); + kfree(ring); +} + +static void nhi_interrupt_work(struct work_struct *work) +{ + struct tb_nhi *nhi = container_of(work, typeof(*nhi), interrupt_work); + int value = 0; /* Suppress uninitialized usage warning. */ + int bit; + int hop = -1; + int type = 0; /* current interrupt type 0: TX, 1: RX, 2: RX overflow */ + struct tb_ring *ring; + + mutex_lock(&nhi->lock); + + /* + * Starting at REG_RING_NOTIFY_BASE there are three status bitfields + * (TX, RX, RX overflow). We iterate over the bits and read a new + * dwords as required. The registers are cleared on read. + */ + for (bit = 0; bit < 3 * nhi->hop_count; bit++) { + if (bit % 32 == 0) + value = ioread32(nhi->iobase + + REG_RING_NOTIFY_BASE + + 4 * (bit / 32)); + if (++hop == nhi->hop_count) { + hop = 0; + type++; + } + if ((value & (1 << (bit % 32))) == 0) + continue; + if (type == 2) { + dev_warn(&nhi->pdev->dev, + "RX overflow for ring %d\n", + hop); + continue; + } + if (type == 0) + ring = nhi->tx_rings[hop]; + else + ring = nhi->rx_rings[hop]; + if (ring == NULL) { + dev_warn(&nhi->pdev->dev, + "got interrupt for inactive %s ring %d\n", + type ? "RX" : "TX", + hop); + continue; + } + /* we do not check ring->running, this is done in ring->work */ + schedule_work(&ring->work); + } + mutex_unlock(&nhi->lock); +} + +static irqreturn_t nhi_msi(int irq, void *data) +{ + struct tb_nhi *nhi = data; + schedule_work(&nhi->interrupt_work); + return IRQ_HANDLED; +} + +static void nhi_shutdown(struct tb_nhi *nhi) +{ + int i; + dev_info(&nhi->pdev->dev, "shutdown\n"); + + for (i = 0; i < nhi->hop_count; i++) { + if (nhi->tx_rings[i]) + dev_WARN(&nhi->pdev->dev, + "TX ring %d is still active\n", i); + if (nhi->rx_rings[i]) + dev_WARN(&nhi->pdev->dev, + "RX ring %d is still active\n", i); + } + nhi_disable_interrupts(nhi); + /* + * We have to release the irq before calling flush_work. Otherwise an + * already executing IRQ handler could call schedule_work again. + */ + devm_free_irq(&nhi->pdev->dev, nhi->pdev->irq, nhi); + flush_work(&nhi->interrupt_work); + mutex_destroy(&nhi->lock); +} + +static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct tb_nhi *nhi; + int res; + + res = pcim_enable_device(pdev); + if (res) { + dev_err(&pdev->dev, "cannot enable PCI device, aborting\n"); + return res; + } + + res = pci_enable_msi(pdev); + if (res) { + dev_err(&pdev->dev, "cannot enable MSI, aborting\n"); + return res; + } + + res = pcim_iomap_regions(pdev, 1 << 0, "thunderbolt"); + if (res) { + dev_err(&pdev->dev, "cannot obtain PCI resources, aborting\n"); + return res; + } + + nhi = devm_kzalloc(&pdev->dev, sizeof(*nhi), GFP_KERNEL); + if (!nhi) + return -ENOMEM; + + nhi->pdev = pdev; + /* cannot fail - table is allocated bin pcim_iomap_regions */ + nhi->iobase = pcim_iomap_table(pdev)[0]; + nhi->hop_count = ioread32(nhi->iobase + REG_HOP_COUNT) & 0x3ff; + if (nhi->hop_count != 12) + dev_warn(&pdev->dev, "unexpected hop count: %d\n", + nhi->hop_count); + INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); + + nhi->tx_rings = devm_kzalloc(&pdev->dev, + nhi->hop_count * sizeof(struct tb_ring), + GFP_KERNEL); + nhi->rx_rings = devm_kzalloc(&pdev->dev, + nhi->hop_count * sizeof(struct tb_ring), + GFP_KERNEL); + if (!nhi->tx_rings || !nhi->rx_rings) + return -ENOMEM; + + nhi_disable_interrupts(nhi); /* In case someone left them on. */ + res = devm_request_irq(&pdev->dev, pdev->irq, nhi_msi, + IRQF_NO_SUSPEND, /* must work during _noirq */ + "thunderbolt", nhi); + if (res) { + dev_err(&pdev->dev, "request_irq failed, aborting\n"); + return res; + } + + mutex_init(&nhi->lock); + + pci_set_master(pdev); + + /* magic value - clock related? */ + iowrite32(3906250 / 10000, nhi->iobase + 0x38c00); + + pci_set_drvdata(pdev, nhi); + + return 0; +} + +static void nhi_remove(struct pci_dev *pdev) +{ + struct tb_nhi *nhi = pci_get_drvdata(pdev); + nhi_shutdown(nhi); +} + +struct pci_device_id nhi_ids[] = { + /* + * We have to specify class, the TB bridges use the same device and + * vendor (sub)id. + */ + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, .device = 0x1547, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { + .class = PCI_CLASS_SYSTEM_OTHER << 8, .class_mask = ~0, + .vendor = PCI_VENDOR_ID_INTEL, .device = 0x156c, + .subvendor = 0x2222, .subdevice = 0x1111, + }, + { 0,} +}; + +MODULE_DEVICE_TABLE(pci, nhi_ids); +MODULE_LICENSE("GPL"); + +static struct pci_driver nhi_driver = { + .name = "thunderbolt", + .id_table = nhi_ids, + .probe = nhi_probe, + .remove = nhi_remove, +}; + +static int __init nhi_init(void) +{ + if (!dmi_match(DMI_BOARD_VENDOR, "Apple Inc.")) + return -ENOSYS; + return pci_register_driver(&nhi_driver); +} + +static void __exit nhi_unload(void) +{ + pci_unregister_driver(&nhi_driver); +} + +module_init(nhi_init); +module_exit(nhi_unload); diff --git a/drivers/thunderbolt/nhi.h b/drivers/thunderbolt/nhi.h new file mode 100644 index 000000000000..317242939b31 --- /dev/null +++ b/drivers/thunderbolt/nhi.h @@ -0,0 +1,114 @@ +/* + * Thunderbolt Cactus Ridge driver - NHI driver + * + * Copyright (c) 2014 Andreas Noever + */ + +#ifndef DSL3510_H_ +#define DSL3510_H_ + +#include +#include + +/** + * struct tb_nhi - thunderbolt native host interface + */ +struct tb_nhi { + struct mutex lock; /* + * Must be held during ring creation/destruction. + * Is acquired by interrupt_work when dispatching + * interrupts to individual rings. + **/ + struct pci_dev *pdev; + void __iomem *iobase; + struct tb_ring **tx_rings; + struct tb_ring **rx_rings; + struct work_struct interrupt_work; + u32 hop_count; /* Number of rings (end point hops) supported by NHI. */ +}; + +/** + * struct tb_ring - thunderbolt TX or RX ring associated with a NHI + */ +struct tb_ring { + struct mutex lock; /* must be acquired after nhi->lock */ + struct tb_nhi *nhi; + int size; + int hop; + int head; /* write next descriptor here */ + int tail; /* complete next descriptor here */ + struct ring_desc *descriptors; + dma_addr_t descriptors_dma; + struct list_head queue; + struct list_head in_flight; + struct work_struct work; + bool is_tx:1; /* rx otherwise */ + bool running:1; +}; + +struct ring_frame; +typedef void (*ring_cb)(struct tb_ring*, struct ring_frame*, bool canceled); + +/** + * struct ring_frame - for use with ring_rx/ring_tx + */ +struct ring_frame { + dma_addr_t buffer_phy; + ring_cb callback; + struct list_head list; + u32 size:12; /* TX: in, RX: out*/ + u32 flags:12; /* RX: out */ + u32 eof:4; /* TX:in, RX: out */ + u32 sof:4; /* TX:in, RX: out */ +}; + +#define TB_FRAME_SIZE 0x100 /* minimum size for ring_rx */ + +struct tb_ring *ring_alloc_tx(struct tb_nhi *nhi, int hop, int size); +struct tb_ring *ring_alloc_rx(struct tb_nhi *nhi, int hop, int size); +void ring_start(struct tb_ring *ring); +void ring_stop(struct tb_ring *ring); +void ring_free(struct tb_ring *ring); + +int __ring_enqueue(struct tb_ring *ring, struct ring_frame *frame); + +/** + * ring_rx() - enqueue a frame on an RX ring + * + * frame->buffer, frame->buffer_phy and frame->callback have to be set. The + * buffer must contain at least TB_FRAME_SIZE bytes. + * + * frame->callback will be invoked with frame->size, frame->flags, frame->eof, + * frame->sof set once the frame has been received. + * + * If ring_stop is called after the packet has been enqueued frame->callback + * will be called with canceled set to true. + * + * Return: Returns ESHUTDOWN if ring_stop has been called. Zero otherwise. + */ +static inline int ring_rx(struct tb_ring *ring, struct ring_frame *frame) +{ + WARN_ON(ring->is_tx); + return __ring_enqueue(ring, frame); +} + +/** + * ring_tx() - enqueue a frame on an TX ring + * + * frame->buffer, frame->buffer_phy, frame->callback, frame->size, frame->eof + * and frame->sof have to be set. + * + * frame->callback will be invoked with once the frame has been transmitted. + * + * If ring_stop is called after the packet has been enqueued frame->callback + * will be called with canceled set to true. + * + * Return: Returns ESHUTDOWN if ring_stop has been called. Zero otherwise. + */ +static inline int ring_tx(struct tb_ring *ring, struct ring_frame *frame) +{ + WARN_ON(!ring->is_tx); + return __ring_enqueue(ring, frame); +} + +#endif diff --git a/drivers/thunderbolt/nhi_regs.h b/drivers/thunderbolt/nhi_regs.h new file mode 100644 index 000000000000..86b996c702a0 --- /dev/null +++ b/drivers/thunderbolt/nhi_regs.h @@ -0,0 +1,101 @@ +/* + * Thunderbolt Cactus Ridge driver - NHI registers + * + * Copyright (c) 2014 Andreas Noever + */ + +#ifndef DSL3510_REGS_H_ +#define DSL3510_REGS_H_ + +#include + +enum ring_flags { + RING_FLAG_ISOCH_ENABLE = 1 << 27, /* TX only? */ + RING_FLAG_E2E_FLOW_CONTROL = 1 << 28, + RING_FLAG_PCI_NO_SNOOP = 1 << 29, + RING_FLAG_RAW = 1 << 30, /* ignore EOF/SOF mask, include checksum */ + RING_FLAG_ENABLE = 1 << 31, +}; + +enum ring_desc_flags { + RING_DESC_ISOCH = 0x1, /* TX only? */ + RING_DESC_COMPLETED = 0x2, /* set by NHI */ + RING_DESC_POSTED = 0x4, /* always set this */ + RING_DESC_INTERRUPT = 0x8, /* request an interrupt on completion */ +}; + +/** + * struct ring_desc - TX/RX ring entry + * + * For TX set length/eof/sof. + * For RX length/eof/sof are set by the NHI. + */ +struct ring_desc { + u64 phys; + u32 length:12; + u32 eof:4; + u32 sof:4; + enum ring_desc_flags flags:12; + u32 time; /* write zero */ +} __packed; + +/* NHI registers in bar 0 */ + +/* + * 16 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 00: physical pointer to an array of struct ring_desc + * 08: ring tail (set by NHI) + * 10: ring head (index of first non posted descriptor) + * 12: descriptor count + */ +#define REG_TX_RING_BASE 0x00000 + +/* + * 16 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 00: physical pointer to an array of struct ring_desc + * 08: ring head (index of first not posted descriptor) + * 10: ring tail (set by NHI) + * 12: descriptor count + * 14: max frame sizes (anything larger than 0x100 has no effect) + */ +#define REG_RX_RING_BASE 0x08000 + +/* + * 32 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 00: enum_ring_flags + * 04: isoch time stamp ?? (write 0) + * ..: unknown + */ +#define REG_TX_OPTIONS_BASE 0x19800 + +/* + * 32 bytes per entry, one entry for every hop (REG_HOP_COUNT) + * 00: enum ring_flags + * If RING_FLAG_E2E_FLOW_CONTROL is set then bits 13-23 must be set to + * the corresponding TX hop id. + * 04: EOF/SOF mask (ignored for RING_FLAG_RAW rings) + * ..: unknown + */ +#define REG_RX_OPTIONS_BASE 0x29800 + +/* + * three bitfields: tx, rx, rx overflow + * Every bitfield contains one bit for every hop (REG_HOP_COUNT). Registers are + * cleared on read. New interrupts are fired only after ALL registers have been + * read (even those containing only disabled rings). + */ +#define REG_RING_NOTIFY_BASE 0x37800 +#define RING_NOTIFY_REG_COUNT(nhi) ((31 + 3 * nhi->hop_count) / 32) + +/* + * two bitfields: rx, tx + * Both bitfields contains one bit for every hop (REG_HOP_COUNT). To + * enable/disable interrupts set/clear the corresponding bits. + */ +#define REG_RING_INTERRUPT_BASE 0x38200 +#define RING_INTERRUPT_REG_COUNT(nhi) ((31 + 2 * nhi->hop_count) / 32) + +/* The last 11 bits contain the number of hops supported by the NHI port. */ +#define REG_HOP_COUNT 0x39640 + +#endif -- cgit v1.2.3 From f25bf6fcb1a83a149bc8b5285d33b48cbd47c7d7 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:03:59 +0200 Subject: thunderbolt: Add control channel interface Thunderbolt devices are configured by reading/writing into their configuration space (similar to pci). This is done by sending packets through the NHI (native host interface) onto the control channel. This patch handles the low level packet based protocol and exposes higher level operations like tb_cfg_read/tb_cfg_write. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/ctl.c | 731 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/ctl.h | 75 +++++ 3 files changed, 807 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/ctl.c create mode 100644 drivers/thunderbolt/ctl.h diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index d473ab93d127..4b21c9926ea8 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o +thunderbolt-objs := nhi.o ctl.o diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c new file mode 100644 index 000000000000..9b0120bede51 --- /dev/null +++ b/drivers/thunderbolt/ctl.c @@ -0,0 +1,731 @@ +/* + * Thunderbolt Cactus Ridge driver - control channel and configuration commands + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include +#include +#include +#include +#include + +#include "ctl.h" + + +struct ctl_pkg { + struct tb_ctl *ctl; + void *buffer; + struct ring_frame frame; +}; + +#define TB_CTL_RX_PKG_COUNT 10 + +/** + * struct tb_cfg - thunderbolt control channel + */ +struct tb_ctl { + struct tb_nhi *nhi; + struct tb_ring *tx; + struct tb_ring *rx; + + struct dma_pool *frame_pool; + struct ctl_pkg *rx_packets[TB_CTL_RX_PKG_COUNT]; + DECLARE_KFIFO(response_fifo, struct ctl_pkg*, 16); + struct completion response_ready; + + hotplug_cb callback; + void *callback_data; +}; + + +#define tb_ctl_WARN(ctl, format, arg...) \ + dev_WARN(&(ctl)->nhi->pdev->dev, format, ## arg) + +#define tb_ctl_err(ctl, format, arg...) \ + dev_err(&(ctl)->nhi->pdev->dev, format, ## arg) + +#define tb_ctl_warn(ctl, format, arg...) \ + dev_warn(&(ctl)->nhi->pdev->dev, format, ## arg) + +#define tb_ctl_info(ctl, format, arg...) \ + dev_info(&(ctl)->nhi->pdev->dev, format, ## arg) + + +/* configuration packets definitions */ + +enum tb_cfg_pkg_type { + TB_CFG_PKG_READ = 1, + TB_CFG_PKG_WRITE = 2, + TB_CFG_PKG_ERROR = 3, + TB_CFG_PKG_NOTIFY_ACK = 4, + TB_CFG_PKG_EVENT = 5, + TB_CFG_PKG_XDOMAIN_REQ = 6, + TB_CFG_PKG_XDOMAIN_RESP = 7, + TB_CFG_PKG_OVERRIDE = 8, + TB_CFG_PKG_RESET = 9, + TB_CFG_PKG_PREPARE_TO_SLEEP = 0xd, +}; + +/* common header */ +struct tb_cfg_header { + u32 route_hi:22; + u32 unknown:10; /* highest order bit is set on replies */ + u32 route_lo; +} __packed; + +/* additional header for read/write packets */ +struct tb_cfg_address { + u32 offset:13; /* in dwords */ + u32 length:6; /* in dwords */ + u32 port:6; + enum tb_cfg_space space:2; + u32 seq:2; /* sequence number */ + u32 zero:3; +} __packed; + +/* TB_CFG_PKG_READ, response for TB_CFG_PKG_WRITE */ +struct cfg_read_pkg { + struct tb_cfg_header header; + struct tb_cfg_address addr; +} __packed; + +/* TB_CFG_PKG_WRITE, response for TB_CFG_PKG_READ */ +struct cfg_write_pkg { + struct tb_cfg_header header; + struct tb_cfg_address addr; + u32 data[64]; /* maximum size, tb_cfg_address.length has 6 bits */ +} __packed; + +/* TB_CFG_PKG_ERROR */ +struct cfg_error_pkg { + struct tb_cfg_header header; + enum tb_cfg_error error:4; + u32 zero1:4; + u32 port:6; + u32 zero2:2; /* Both should be zero, still they are different fields. */ + u32 zero3:16; +} __packed; + +/* TB_CFG_PKG_EVENT */ +struct cfg_event_pkg { + struct tb_cfg_header header; + u32 port:6; + u32 zero:25; + bool unplug:1; +} __packed; + +/* TB_CFG_PKG_RESET */ +struct cfg_reset_pkg { + struct tb_cfg_header header; +} __packed; + +/* TB_CFG_PKG_PREPARE_TO_SLEEP */ +struct cfg_pts_pkg { + struct tb_cfg_header header; + u32 data; +} __packed; + + +/* utility functions */ + +static u64 get_route(struct tb_cfg_header header) +{ + return (u64) header.route_hi << 32 | header.route_lo; +} + +static struct tb_cfg_header make_header(u64 route) +{ + struct tb_cfg_header header = { + .route_hi = route >> 32, + .route_lo = route, + }; + /* check for overflow, route_hi is not 32 bits! */ + WARN_ON(get_route(header) != route); + return header; +} + +static int check_header(struct ctl_pkg *pkg, u32 len, enum tb_cfg_pkg_type type, + u64 route) +{ + struct tb_cfg_header *header = pkg->buffer; + + /* check frame, TODO: frame flags */ + if (WARN(len != pkg->frame.size, + "wrong framesize (expected %#x, got %#x)\n", + len, pkg->frame.size)) + return -EIO; + if (WARN(type != pkg->frame.eof, "wrong eof (expected %#x, got %#x)\n", + type, pkg->frame.eof)) + return -EIO; + if (WARN(pkg->frame.sof, "wrong sof (expected 0x0, got %#x)\n", + pkg->frame.sof)) + return -EIO; + + /* check header */ + if (WARN(header->unknown != 1 << 9, + "header->unknown is %#x\n", header->unknown)) + return -EIO; + if (WARN(route != get_route(*header), + "wrong route (expected %llx, got %llx)", + route, get_route(*header))) + return -EIO; + return 0; +} + +static int check_config_address(struct tb_cfg_address addr, + enum tb_cfg_space space, u32 offset, + u32 length) +{ + if (WARN(addr.zero, "addr.zero is %#x\n", addr.zero)) + return -EIO; + if (WARN(space != addr.space, "wrong space (expected %x, got %x\n)", + space, addr.space)) + return -EIO; + if (WARN(offset != addr.offset, "wrong offset (expected %x, got %x\n)", + offset, addr.offset)) + return -EIO; + if (WARN(length != addr.length, "wrong space (expected %x, got %x\n)", + length, addr.length)) + return -EIO; + if (WARN(addr.seq, "addr.seq is %#x\n", addr.seq)) + return -EIO; + /* + * We cannot check addr->port as it is set to the upstream port of the + * sender. + */ + return 0; +} + +static struct tb_cfg_result decode_error(struct ctl_pkg *response) +{ + struct cfg_error_pkg *pkg = response->buffer; + struct tb_cfg_result res = { 0 }; + res.response_route = get_route(pkg->header); + res.response_port = 0; + res.err = check_header(response, sizeof(*pkg), TB_CFG_PKG_ERROR, + get_route(pkg->header)); + if (res.err) + return res; + + WARN(pkg->zero1, "pkg->zero1 is %#x\n", pkg->zero1); + WARN(pkg->zero2, "pkg->zero1 is %#x\n", pkg->zero1); + WARN(pkg->zero3, "pkg->zero1 is %#x\n", pkg->zero1); + res.err = 1; + res.tb_error = pkg->error; + res.response_port = pkg->port; + return res; + +} + +static struct tb_cfg_result parse_header(struct ctl_pkg *pkg, u32 len, + enum tb_cfg_pkg_type type, u64 route) +{ + struct tb_cfg_header *header = pkg->buffer; + struct tb_cfg_result res = { 0 }; + + if (pkg->frame.eof == TB_CFG_PKG_ERROR) + return decode_error(pkg); + + res.response_port = 0; /* will be updated later for cfg_read/write */ + res.response_route = get_route(*header); + res.err = check_header(pkg, len, type, route); + return res; +} + +static void tb_cfg_print_error(struct tb_ctl *ctl, + const struct tb_cfg_result *res) +{ + WARN_ON(res->err != 1); + switch (res->tb_error) { + case TB_CFG_ERROR_PORT_NOT_CONNECTED: + /* Port is not connected. This can happen during surprise + * removal. Do not warn. */ + return; + case TB_CFG_ERROR_INVALID_CONFIG_SPACE: + /* + * Invalid cfg_space/offset/length combination in + * cfg_read/cfg_write. + */ + tb_ctl_WARN(ctl, + "CFG_ERROR(%llx:%x): Invalid config space of offset\n", + res->response_route, res->response_port); + return; + case TB_CFG_ERROR_NO_SUCH_PORT: + /* + * - The route contains a non-existent port. + * - The route contains a non-PHY port (e.g. PCIe). + * - The port in cfg_read/cfg_write does not exist. + */ + tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Invalid port\n", + res->response_route, res->response_port); + return; + case TB_CFG_ERROR_LOOP: + tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Route contains a loop\n", + res->response_route, res->response_port); + return; + default: + /* 5,6,7,9 and 11 are also valid error codes */ + tb_ctl_WARN(ctl, "CFG_ERROR(%llx:%x): Unknown error\n", + res->response_route, res->response_port); + return; + } +} + +static void cpu_to_be32_array(__be32 *dst, u32 *src, size_t len) +{ + int i; + for (i = 0; i < len; i++) + dst[i] = cpu_to_be32(src[i]); +} + +static void be32_to_cpu_array(u32 *dst, __be32 *src, size_t len) +{ + int i; + for (i = 0; i < len; i++) + dst[i] = be32_to_cpu(src[i]); +} + +static __be32 tb_crc(void *data, size_t len) +{ + return cpu_to_be32(~__crc32c_le(~0, data, len)); +} + +static void tb_ctl_pkg_free(struct ctl_pkg *pkg) +{ + if (pkg) { + dma_pool_free(pkg->ctl->frame_pool, + pkg->buffer, pkg->frame.buffer_phy); + kfree(pkg); + } +} + +static struct ctl_pkg *tb_ctl_pkg_alloc(struct tb_ctl *ctl) +{ + struct ctl_pkg *pkg = kzalloc(sizeof(*pkg), GFP_KERNEL); + if (!pkg) + return 0; + pkg->ctl = ctl; + pkg->buffer = dma_pool_alloc(ctl->frame_pool, GFP_KERNEL, + &pkg->frame.buffer_phy); + if (!pkg->buffer) { + kfree(pkg); + return 0; + } + return pkg; +} + + +/* RX/TX handling */ + +static void tb_ctl_tx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct ctl_pkg *pkg = container_of(frame, typeof(*pkg), frame); + tb_ctl_pkg_free(pkg); +} + +/** + * tb_cfg_tx() - transmit a packet on the control channel + * + * len must be a multiple of four. + * + * Return: Returns 0 on success or an error code on failure. + */ +static int tb_ctl_tx(struct tb_ctl *ctl, void *data, size_t len, + enum tb_cfg_pkg_type type) +{ + int res; + struct ctl_pkg *pkg; + if (len % 4 != 0) { /* required for le->be conversion */ + tb_ctl_WARN(ctl, "TX: invalid size: %zu\n", len); + return -EINVAL; + } + if (len > TB_FRAME_SIZE - 4) { /* checksum is 4 bytes */ + tb_ctl_WARN(ctl, "TX: packet too large: %zu/%d\n", + len, TB_FRAME_SIZE - 4); + return -EINVAL; + } + pkg = tb_ctl_pkg_alloc(ctl); + if (!pkg) + return -ENOMEM; + pkg->frame.callback = tb_ctl_tx_callback; + pkg->frame.size = len + 4; + pkg->frame.sof = type; + pkg->frame.eof = type; + cpu_to_be32_array(pkg->buffer, data, len / 4); + *(u32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len); + + res = ring_tx(ctl->tx, &pkg->frame); + if (res) /* ring is stopped */ + tb_ctl_pkg_free(pkg); + return res; +} + +/** + * tb_ctl_handle_plug_event() - acknowledge a plug event, invoke ctl->callback + */ +static void tb_ctl_handle_plug_event(struct tb_ctl *ctl, + struct ctl_pkg *response) +{ + struct cfg_event_pkg *pkg = response->buffer; + u64 route = get_route(pkg->header); + + if (check_header(response, sizeof(*pkg), TB_CFG_PKG_EVENT, route)) { + tb_ctl_warn(ctl, "malformed TB_CFG_PKG_EVENT\n"); + return; + } + + if (tb_cfg_error(ctl, route, pkg->port, TB_CFG_ERROR_ACK_PLUG_EVENT)) + tb_ctl_warn(ctl, "could not ack plug event on %llx:%x\n", + route, pkg->port); + WARN(pkg->zero, "pkg->zero is %#x\n", pkg->zero); + ctl->callback(ctl->callback_data, route, pkg->port, pkg->unplug); +} + +static void tb_ctl_rx_submit(struct ctl_pkg *pkg) +{ + ring_rx(pkg->ctl->rx, &pkg->frame); /* + * We ignore failures during stop. + * All rx packets are referenced + * from ctl->rx_packets, so we do + * not loose them. + */ +} + +static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, + bool canceled) +{ + struct ctl_pkg *pkg = container_of(frame, typeof(*pkg), frame); + + if (canceled) + return; /* + * ring is stopped, packet is referenced from + * ctl->rx_packets. + */ + + if (frame->size < 4 || frame->size % 4 != 0) { + tb_ctl_err(pkg->ctl, "RX: invalid size %#x, dropping packet\n", + frame->size); + goto rx; + } + + frame->size -= 4; /* remove checksum */ + if (*(u32 *) (pkg->buffer + frame->size) + != tb_crc(pkg->buffer, frame->size)) { + tb_ctl_err(pkg->ctl, + "RX: checksum mismatch, dropping packet\n"); + goto rx; + } + be32_to_cpu_array(pkg->buffer, pkg->buffer, frame->size / 4); + + if (frame->eof == TB_CFG_PKG_EVENT) { + tb_ctl_handle_plug_event(pkg->ctl, pkg); + goto rx; + } + if (!kfifo_put(&pkg->ctl->response_fifo, pkg)) { + tb_ctl_err(pkg->ctl, "RX: fifo is full\n"); + goto rx; + } + complete(&pkg->ctl->response_ready); + return; +rx: + tb_ctl_rx_submit(pkg); +} + +/** + * tb_ctl_rx() - receive a packet from the control channel + */ +static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer, + size_t length, int timeout_msec, + u8 route, enum tb_cfg_pkg_type type) +{ + struct tb_cfg_result res; + struct ctl_pkg *pkg; + + if (!wait_for_completion_timeout(&ctl->response_ready, + msecs_to_jiffies(timeout_msec))) { + tb_ctl_WARN(ctl, "RX: timeout\n"); + return (struct tb_cfg_result) { .err = -ETIMEDOUT }; + } + if (!kfifo_get(&ctl->response_fifo, &pkg)) { + tb_ctl_WARN(ctl, "empty kfifo\n"); + return (struct tb_cfg_result) { .err = -EIO }; + } + + res = parse_header(pkg, length, type, route); + if (!res.err) + memcpy(buffer, pkg->buffer, length); + tb_ctl_rx_submit(pkg); + return res; +} + + +/* public interface, alloc/start/stop/free */ + +/** + * tb_ctl_alloc() - allocate a control channel + * + * cb will be invoked once for every hot plug event. + * + * Return: Returns a pointer on success or NULL on failure. + */ +struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data) +{ + int i; + struct tb_ctl *ctl = kzalloc(sizeof(*ctl), GFP_KERNEL); + if (!ctl) + return NULL; + ctl->nhi = nhi; + ctl->callback = cb; + ctl->callback_data = cb_data; + + init_completion(&ctl->response_ready); + INIT_KFIFO(ctl->response_fifo); + ctl->frame_pool = dma_pool_create("thunderbolt_ctl", &nhi->pdev->dev, + TB_FRAME_SIZE, 4, 0); + if (!ctl->frame_pool) + goto err; + + ctl->tx = ring_alloc_tx(nhi, 0, 10); + if (!ctl->tx) + goto err; + + ctl->rx = ring_alloc_rx(nhi, 0, 10); + if (!ctl->rx) + goto err; + + for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++) { + ctl->rx_packets[i] = tb_ctl_pkg_alloc(ctl); + if (!ctl->rx_packets[i]) + goto err; + ctl->rx_packets[i]->frame.callback = tb_ctl_rx_callback; + } + + tb_ctl_info(ctl, "control channel created\n"); + return ctl; +err: + tb_ctl_free(ctl); + return NULL; +} + +/** + * tb_ctl_free() - free a control channel + * + * Must be called after tb_ctl_stop. + * + * Must NOT be called from ctl->callback. + */ +void tb_ctl_free(struct tb_ctl *ctl) +{ + int i; + if (ctl->rx) + ring_free(ctl->rx); + if (ctl->tx) + ring_free(ctl->tx); + + /* free RX packets */ + for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++) + tb_ctl_pkg_free(ctl->rx_packets[i]); + + + if (ctl->frame_pool) + dma_pool_destroy(ctl->frame_pool); + kfree(ctl); +} + +/** + * tb_cfg_start() - start/resume the control channel + */ +void tb_ctl_start(struct tb_ctl *ctl) +{ + int i; + tb_ctl_info(ctl, "control channel starting...\n"); + ring_start(ctl->tx); /* is used to ack hotplug packets, start first */ + ring_start(ctl->rx); + for (i = 0; i < TB_CTL_RX_PKG_COUNT; i++) + tb_ctl_rx_submit(ctl->rx_packets[i]); +} + +/** + * control() - pause the control channel + * + * All invocations of ctl->callback will have finished after this method + * returns. + * + * Must NOT be called from ctl->callback. + */ +void tb_ctl_stop(struct tb_ctl *ctl) +{ + ring_stop(ctl->rx); + ring_stop(ctl->tx); + + if (!kfifo_is_empty(&ctl->response_fifo)) + tb_ctl_WARN(ctl, "dangling response in response_fifo\n"); + kfifo_reset(&ctl->response_fifo); + tb_ctl_info(ctl, "control channel stopped\n"); +} + +/* public interface, commands */ + +/** + * tb_cfg_error() - send error packet + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port, + enum tb_cfg_error error) +{ + struct cfg_error_pkg pkg = { + .header = make_header(route), + .port = port, + .error = error, + }; + tb_ctl_info(ctl, "resetting error on %llx:%x.\n", route, port); + return tb_ctl_tx(ctl, &pkg, sizeof(pkg), TB_CFG_PKG_ERROR); +} + +/** + * tb_cfg_reset() - send a reset packet and wait for a response + * + * If the switch at route is incorrectly configured then we will not receive a + * reply (even though the switch will reset). The caller should check for + * -ETIMEDOUT and attempt to reconfigure the switch. + */ +struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route, + int timeout_msec) +{ + int err; + struct cfg_reset_pkg request = { .header = make_header(route) }; + struct tb_cfg_header reply; + + err = tb_ctl_tx(ctl, &request, sizeof(request), TB_CFG_PKG_RESET); + if (err) + return (struct tb_cfg_result) { .err = err }; + + return tb_ctl_rx(ctl, &reply, sizeof(reply), timeout_msec, route, + TB_CFG_PKG_RESET); +} + +/** + * tb_cfg_read() - read from config space into buffer + * + * Offset and length are in dwords. + */ +struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, + u64 route, u32 port, enum tb_cfg_space space, + u32 offset, u32 length, int timeout_msec) +{ + struct tb_cfg_result res = { 0 }; + struct cfg_read_pkg request = { + .header = make_header(route), + .addr = { + .port = port, + .space = space, + .offset = offset, + .length = length, + }, + }; + struct cfg_write_pkg reply; + + res.err = tb_ctl_tx(ctl, &request, sizeof(request), TB_CFG_PKG_READ); + if (res.err) + return res; + + res = tb_ctl_rx(ctl, &reply, 12 + 4 * length, timeout_msec, route, + TB_CFG_PKG_READ); + if (res.err) + return res; + + res.response_port = reply.addr.port; + res.err = check_config_address(reply.addr, space, offset, length); + if (!res.err) + memcpy(buffer, &reply.data, 4 * length); + return res; +} + +/** + * tb_cfg_write() - write from buffer into config space + * + * Offset and length are in dwords. + */ +struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer, + u64 route, u32 port, enum tb_cfg_space space, + u32 offset, u32 length, int timeout_msec) +{ + struct tb_cfg_result res = { 0 }; + struct cfg_write_pkg request = { + .header = make_header(route), + .addr = { + .port = port, + .space = space, + .offset = offset, + .length = length, + }, + }; + struct cfg_read_pkg reply; + + memcpy(&request.data, buffer, length * 4); + + res.err = tb_ctl_tx(ctl, &request, 12 + 4 * length, TB_CFG_PKG_WRITE); + if (res.err) + return res; + + res = tb_ctl_rx(ctl, &reply, sizeof(reply), timeout_msec, route, + TB_CFG_PKG_WRITE); + if (res.err) + return res; + + res.response_port = reply.addr.port; + res.err = check_config_address(reply.addr, space, offset, length); + return res; +} + +int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, + enum tb_cfg_space space, u32 offset, u32 length) +{ + struct tb_cfg_result res = tb_cfg_read_raw(ctl, buffer, route, port, + space, offset, length, TB_CFG_DEFAULT_TIMEOUT); + if (res.err == 1) { + tb_cfg_print_error(ctl, &res); + return -EIO; + } + WARN(res.err, "tb_cfg_read: %d\n", res.err); + return res.err; +} + +int tb_cfg_write(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, + enum tb_cfg_space space, u32 offset, u32 length) +{ + struct tb_cfg_result res = tb_cfg_write_raw(ctl, buffer, route, port, + space, offset, length, TB_CFG_DEFAULT_TIMEOUT); + if (res.err == 1) { + tb_cfg_print_error(ctl, &res); + return -EIO; + } + WARN(res.err, "tb_cfg_write: %d\n", res.err); + return res.err; +} + +/** + * tb_cfg_get_upstream_port() - get upstream port number of switch at route + * + * Reads the first dword from the switches TB_CFG_SWITCH config area and + * returns the port number from which the reply originated. + * + * Return: Returns the upstream port number on success or an error code on + * failure. + */ +int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route) +{ + u32 dummy; + struct tb_cfg_result res = tb_cfg_read_raw(ctl, &dummy, route, 0, + TB_CFG_SWITCH, 0, 1, + TB_CFG_DEFAULT_TIMEOUT); + if (res.err == 1) + return -EIO; + if (res.err) + return res.err; + return res.response_port; +} diff --git a/drivers/thunderbolt/ctl.h b/drivers/thunderbolt/ctl.h new file mode 100644 index 000000000000..ba87d6e731dd --- /dev/null +++ b/drivers/thunderbolt/ctl.h @@ -0,0 +1,75 @@ +/* + * Thunderbolt Cactus Ridge driver - control channel and configuration commands + * + * Copyright (c) 2014 Andreas Noever + */ + +#ifndef _TB_CFG +#define _TB_CFG + +#include "nhi.h" + +/* control channel */ +struct tb_ctl; + +typedef void (*hotplug_cb)(void *data, u64 route, u8 port, bool unplug); + +struct tb_ctl *tb_ctl_alloc(struct tb_nhi *nhi, hotplug_cb cb, void *cb_data); +void tb_ctl_start(struct tb_ctl *ctl); +void tb_ctl_stop(struct tb_ctl *ctl); +void tb_ctl_free(struct tb_ctl *ctl); + +/* configuration commands */ + +#define TB_CFG_DEFAULT_TIMEOUT 5000 /* msec */ + +enum tb_cfg_space { + TB_CFG_HOPS = 0, + TB_CFG_PORT = 1, + TB_CFG_SWITCH = 2, + TB_CFG_COUNTERS = 3, +}; + +enum tb_cfg_error { + TB_CFG_ERROR_PORT_NOT_CONNECTED = 0, + TB_CFG_ERROR_INVALID_CONFIG_SPACE = 2, + TB_CFG_ERROR_NO_SUCH_PORT = 4, + TB_CFG_ERROR_ACK_PLUG_EVENT = 7, /* send as reply to TB_CFG_PKG_EVENT */ + TB_CFG_ERROR_LOOP = 8, +}; + +struct tb_cfg_result { + u64 response_route; + u32 response_port; /* + * If err = 1 then this is the port that send the + * error. + * If err = 0 and if this was a cfg_read/write then + * this is the the upstream port of the responding + * switch. + * Otherwise the field is set to zero. + */ + int err; /* negative errors, 0 for success, 1 for tb errors */ + enum tb_cfg_error tb_error; /* valid if err == 1 */ +}; + + +int tb_cfg_error(struct tb_ctl *ctl, u64 route, u32 port, + enum tb_cfg_error error); +struct tb_cfg_result tb_cfg_reset(struct tb_ctl *ctl, u64 route, + int timeout_msec); +struct tb_cfg_result tb_cfg_read_raw(struct tb_ctl *ctl, void *buffer, + u64 route, u32 port, + enum tb_cfg_space space, u32 offset, + u32 length, int timeout_msec); +struct tb_cfg_result tb_cfg_write_raw(struct tb_ctl *ctl, void *buffer, + u64 route, u32 port, + enum tb_cfg_space space, u32 offset, + u32 length, int timeout_msec); +int tb_cfg_read(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, + enum tb_cfg_space space, u32 offset, u32 length); +int tb_cfg_write(struct tb_ctl *ctl, void *buffer, u64 route, u32 port, + enum tb_cfg_space space, u32 offset, u32 length); +int tb_cfg_get_upstream_port(struct tb_ctl *ctl, u64 route); + + +#endif -- cgit v1.2.3 From d6cc51cd1a4aed1d9e2dd66d643d729acb4be560 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:00 +0200 Subject: thunderbolt: Setup control channel Add struct tb which will contain our view of the thunderbolt bus. For now it just contains a pointer to the control channel and a workqueue for hotplug events. Add thunderbolt_alloc_and_start() and thunderbolt_shutdown_and_free() which are responsible for setup and teardown of struct tb. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/nhi.c | 18 +++++- drivers/thunderbolt/tb.c | 134 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 35 +++++++++++ 4 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 drivers/thunderbolt/tb.c create mode 100644 drivers/thunderbolt/tb.h diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 4b21c9926ea8..1f996bb96ac9 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o +thunderbolt-objs := nhi.o ctl.o tb.o diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 11070ff2cec7..d2b9ce857818 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -16,6 +16,7 @@ #include "nhi.h" #include "nhi_regs.h" +#include "tb.h" #define RING_TYPE(ring) ((ring)->is_tx ? "TX ring" : "RX ring") @@ -517,6 +518,7 @@ static void nhi_shutdown(struct tb_nhi *nhi) static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) { struct tb_nhi *nhi; + struct tb *tb; int res; res = pcim_enable_device(pdev); @@ -575,14 +577,26 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) /* magic value - clock related? */ iowrite32(3906250 / 10000, nhi->iobase + 0x38c00); - pci_set_drvdata(pdev, nhi); + dev_info(&nhi->pdev->dev, "NHI initialized, starting thunderbolt\n"); + tb = thunderbolt_alloc_and_start(nhi); + if (!tb) { + /* + * At this point the RX/TX rings might already have been + * activated. Do a proper shutdown. + */ + nhi_shutdown(nhi); + return -EIO; + } + pci_set_drvdata(pdev, tb); return 0; } static void nhi_remove(struct pci_dev *pdev) { - struct tb_nhi *nhi = pci_get_drvdata(pdev); + struct tb *tb = pci_get_drvdata(pdev); + struct tb_nhi *nhi = tb->nhi; + thunderbolt_shutdown_and_free(tb); nhi_shutdown(nhi); } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c new file mode 100644 index 000000000000..6920979c5b14 --- /dev/null +++ b/drivers/thunderbolt/tb.c @@ -0,0 +1,134 @@ +/* + * Thunderbolt Cactus Ridge driver - bus logic (NHI independent) + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include +#include + +#include "tb.h" + +/* hotplug handling */ + +struct tb_hotplug_event { + struct work_struct work; + struct tb *tb; + u64 route; + u8 port; + bool unplug; +}; + +/** + * tb_handle_hotplug() - handle hotplug event + * + * Executes on tb->wq. + */ +static void tb_handle_hotplug(struct work_struct *work) +{ + struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work); + struct tb *tb = ev->tb; + mutex_lock(&tb->lock); + if (!tb->hotplug_active) + goto out; /* during init, suspend or shutdown */ + + /* do nothing for now */ +out: + mutex_unlock(&tb->lock); + kfree(ev); +} + +/** + * tb_schedule_hotplug_handler() - callback function for the control channel + * + * Delegates to tb_handle_hotplug. + */ +static void tb_schedule_hotplug_handler(void *data, u64 route, u8 port, + bool unplug) +{ + struct tb *tb = data; + struct tb_hotplug_event *ev = kmalloc(sizeof(*ev), GFP_KERNEL); + if (!ev) + return; + INIT_WORK(&ev->work, tb_handle_hotplug); + ev->tb = tb; + ev->route = route; + ev->port = port; + ev->unplug = unplug; + queue_work(tb->wq, &ev->work); +} + +/** + * thunderbolt_shutdown_and_free() - shutdown everything + * + * Free all switches and the config channel. + * + * Used in the error path of thunderbolt_alloc_and_start. + */ +void thunderbolt_shutdown_and_free(struct tb *tb) +{ + mutex_lock(&tb->lock); + + if (tb->ctl) { + tb_ctl_stop(tb->ctl); + tb_ctl_free(tb->ctl); + } + tb->ctl = NULL; + tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */ + + /* allow tb_handle_hotplug to acquire the lock */ + mutex_unlock(&tb->lock); + if (tb->wq) { + flush_workqueue(tb->wq); + destroy_workqueue(tb->wq); + tb->wq = NULL; + } + mutex_destroy(&tb->lock); + kfree(tb); +} + +/** + * thunderbolt_alloc_and_start() - setup the thunderbolt bus + * + * Allocates a tb_cfg control channel, initializes the root switch, enables + * plug events and activates pci devices. + * + * Return: Returns NULL on error. + */ +struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) +{ + struct tb *tb; + + tb = kzalloc(sizeof(*tb), GFP_KERNEL); + if (!tb) + return NULL; + + tb->nhi = nhi; + mutex_init(&tb->lock); + mutex_lock(&tb->lock); + + tb->wq = alloc_ordered_workqueue("thunderbolt", 0); + if (!tb->wq) + goto err_locked; + + tb->ctl = tb_ctl_alloc(tb->nhi, tb_schedule_hotplug_handler, tb); + if (!tb->ctl) + goto err_locked; + /* + * tb_schedule_hotplug_handler may be called as soon as the config + * channel is started. Thats why we have to hold the lock here. + */ + tb_ctl_start(tb->ctl); + + /* Allow tb_handle_hotplug to progress events */ + tb->hotplug_active = true; + mutex_unlock(&tb->lock); + return tb; + +err_locked: + mutex_unlock(&tb->lock); + thunderbolt_shutdown_and_free(tb); + return NULL; +} + diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h new file mode 100644 index 000000000000..6055dfd713fc --- /dev/null +++ b/drivers/thunderbolt/tb.h @@ -0,0 +1,35 @@ +/* + * Thunderbolt Cactus Ridge driver - bus logic (NHI independent) + * + * Copyright (c) 2014 Andreas Noever + */ + +#ifndef TB_H_ +#define TB_H_ + +#include "ctl.h" + +/** + * struct tb - main thunderbolt bus structure + */ +struct tb { + struct mutex lock; /* + * Big lock. Must be held when accessing cfg or + * any struct tb_switch / struct tb_port. + */ + struct tb_nhi *nhi; + struct tb_ctl *ctl; + struct workqueue_struct *wq; /* ordered workqueue for plug events */ + bool hotplug_active; /* + * tb_handle_hotplug will stop progressing plug + * events and exit if this is not set (it needs to + * acquire the lock one more time). Used to drain + * wq after cfg has been paused. + */ + +}; + +struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); +void thunderbolt_shutdown_and_free(struct tb *tb); + +#endif -- cgit v1.2.3 From 7adf60972c692b0b3d0958cd7322e22a67187111 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:01 +0200 Subject: thunderbolt: Add tb_regs.h Every thunderbolt device consists (logically) of a switch with multiple ports. Every port contains up to four config regions (HOPS, PORT, SWITCH, COUNTERS) which are used to configure the device. The tb_regs.h file contains all known registers and capabilities from these config regions. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/tb.c | 5 + drivers/thunderbolt/tb_regs.h | 213 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 drivers/thunderbolt/tb_regs.h diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 6920979c5b14..164dea083e9e 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -9,6 +9,7 @@ #include #include "tb.h" +#include "tb_regs.h" /* hotplug handling */ @@ -100,6 +101,10 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) { struct tb *tb; + BUILD_BUG_ON(sizeof(struct tb_regs_switch_header) != 5 * 4); + BUILD_BUG_ON(sizeof(struct tb_regs_port_header) != 8 * 4); + BUILD_BUG_ON(sizeof(struct tb_regs_hop) != 2 * 4); + tb = kzalloc(sizeof(*tb), GFP_KERNEL); if (!tb) return NULL; diff --git a/drivers/thunderbolt/tb_regs.h b/drivers/thunderbolt/tb_regs.h new file mode 100644 index 000000000000..6577af75d9dc --- /dev/null +++ b/drivers/thunderbolt/tb_regs.h @@ -0,0 +1,213 @@ +/* + * Thunderbolt Cactus Ridge driver - Port/Switch config area registers + * + * Every thunderbolt device consists (logically) of a switch with multiple + * ports. Every port contains up to four config regions (HOPS, PORT, SWITCH, + * COUNTERS) which are used to configure the device. + * + * Copyright (c) 2014 Andreas Noever + */ + +#ifndef _TB_REGS +#define _TB_REGS + +#include + + +#define TB_ROUTE_SHIFT 8 /* number of bits in a port entry of a route */ + + +/* + * TODO: should be 63? But we do not know how to receive frames larger than 256 + * bytes at the frame level. (header + checksum = 16, 60*4 = 240) + */ +#define TB_MAX_CONFIG_RW_LENGTH 60 + +enum tb_cap { + TB_CAP_PHY = 0x0001, + TB_CAP_TIME1 = 0x0003, + TB_CAP_PCIE = 0x0004, + TB_CAP_I2C = 0x0005, + TB_CAP_PLUG_EVENTS = 0x0105, /* also EEPROM */ + TB_CAP_TIME2 = 0x0305, + TB_CAL_IECS = 0x0405, + TB_CAP_LINK_CONTROLLER = 0x0605, /* also IECS */ +}; + +enum tb_port_state { + TB_PORT_DISABLED = 0, /* tb_cap_phy.disable == 1 */ + TB_PORT_CONNECTING = 1, /* retry */ + TB_PORT_UP = 2, + TB_PORT_UNPLUGGED = 7, +}; + +/* capability headers */ + +struct tb_cap_basic { + u8 next; + /* enum tb_cap cap:8; prevent "narrower than values of its type" */ + u8 cap; /* if cap == 0x05 then we have a extended capability */ +} __packed; + +struct tb_cap_extended_short { + u8 next; /* if next and length are zero then we have a long cap */ + enum tb_cap cap:16; + u8 length; +} __packed; + +struct tb_cap_extended_long { + u8 zero1; + enum tb_cap cap:16; + u8 zero2; + u16 next; + u16 length; +} __packed; + +/* capabilities */ + +struct tb_cap_link_controller { + struct tb_cap_extended_long cap_header; + u32 count:4; /* number of link controllers */ + u32 unknown1:4; + u32 base_offset:8; /* + * offset (into this capability) of the configuration + * area of the first link controller + */ + u32 length:12; /* link controller configuration area length */ + u32 unknown2:4; /* TODO check that length is correct */ +} __packed; + +struct tb_cap_phy { + struct tb_cap_basic cap_header; + u32 unknown1:16; + u32 unknown2:14; + bool disable:1; + u32 unknown3:11; + enum tb_port_state state:4; + u32 unknown4:2; +} __packed; + +struct tb_eeprom_ctl { + bool clock:1; /* send pulse to transfer one bit */ + bool access_low:1; /* set to 0 before access */ + bool data_out:1; /* to eeprom */ + bool data_in:1; /* from eeprom */ + bool access_high:1; /* set to 1 before access */ + bool not_present:1; /* should be 0 */ + bool unknown1:1; + bool present:1; /* should be 1 */ + u32 unknown2:24; +} __packed; + +struct tb_cap_plug_events { + struct tb_cap_extended_short cap_header; + u32 __unknown1:2; + u32 plug_events:5; + u32 __unknown2:25; + u32 __unknown3; + u32 __unknown4; + struct tb_eeprom_ctl eeprom_ctl; + u32 __unknown5[7]; + u32 drom_offset; /* 32 bit register, but eeprom addresses are 16 bit */ +} __packed; + +/* device headers */ + +/* Present on port 0 in TB_CFG_SWITCH at address zero. */ +struct tb_regs_switch_header { + /* DWORD 0 */ + u16 vendor_id; + u16 device_id; + /* DWORD 1 */ + u32 first_cap_offset:8; + u32 upstream_port_number:6; + u32 max_port_number:6; + u32 depth:3; + u32 __unknown1:1; + u32 revision:8; + /* DWORD 2 */ + u32 route_lo; + /* DWORD 3 */ + u32 route_hi:31; + bool enabled:1; + /* DWORD 4 */ + u32 plug_events_delay:8; /* + * RW, pause between plug events in + * milliseconds. Writing 0x00 is interpreted + * as 255ms. + */ + u32 __unknown4:16; + u32 thunderbolt_version:8; +} __packed; + +enum tb_port_type { + TB_TYPE_INACTIVE = 0x000000, + TB_TYPE_PORT = 0x000001, + TB_TYPE_NHI = 0x000002, + /* TB_TYPE_ETHERNET = 0x020000, lower order bits are not known */ + /* TB_TYPE_SATA = 0x080000, lower order bits are not known */ + TB_TYPE_DP_HDMI_IN = 0x0e0101, + TB_TYPE_DP_HDMI_OUT = 0x0e0102, + TB_TYPE_PCIE_DOWN = 0x100101, + TB_TYPE_PCIE_UP = 0x100102, + /* TB_TYPE_USB = 0x200000, lower order bits are not known */ +}; + +/* Present on every port in TB_CF_PORT at address zero. */ +struct tb_regs_port_header { + /* DWORD 0 */ + u16 vendor_id; + u16 device_id; + /* DWORD 1 */ + u32 first_cap_offset:8; + u32 max_counters:11; + u32 __unknown1:5; + u32 revision:8; + /* DWORD 2 */ + enum tb_port_type type:24; + u32 thunderbolt_version:8; + /* DWORD 3 */ + u32 __unknown2:20; + u32 port_number:6; + u32 __unknown3:6; + /* DWORD 4 */ + u32 nfc_credits; + /* DWORD 5 */ + u32 max_in_hop_id:11; + u32 max_out_hop_id:11; + u32 __unkown4:10; + /* DWORD 6 */ + u32 __unknown5; + /* DWORD 7 */ + u32 __unknown6; + +} __packed; + +/* Hop register from TB_CFG_HOPS. 8 byte per entry. */ +struct tb_regs_hop { + /* DWORD 0 */ + u32 next_hop:11; /* + * hop to take after sending the packet through + * out_port (on the incoming port of the next switch) + */ + u32 out_port:6; /* next port of the path (on the same switch) */ + u32 initial_credits:8; + u32 unknown1:6; /* set to zero */ + bool enable:1; + + /* DWORD 1 */ + u32 weight:4; + u32 unknown2:4; /* set to zero */ + u32 priority:3; + bool drop_packages:1; + u32 counter:11; /* index into TB_CFG_COUNTERS on this port */ + bool counter_enable:1; + bool ingress_fc:1; + bool egress_fc:1; + bool ingress_shared_buffer:1; + bool egress_shared_buffer:1; + u32 unknown3:4; /* set to zero */ +} __packed; + + +#endif -- cgit v1.2.3 From a25c8b2fc9636aaf29d9d9d89f92cdfd27a2a23d Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:02 +0200 Subject: thunderbolt: Initialize root switch and ports This patch adds the structures tb_switch and tb_port as well as code to initialize the root switch. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/switch.c | 186 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 8 ++ drivers/thunderbolt/tb.h | 138 ++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/switch.c diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 1f996bb96ac9..4ac18d969306 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c new file mode 100644 index 000000000000..e89121ffe44e --- /dev/null +++ b/drivers/thunderbolt/switch.c @@ -0,0 +1,186 @@ +/* + * Thunderbolt Cactus Ridge driver - switch/port utility functions + * + * Copyright (c) 2014 Andreas Noever + */ + +#include + +#include "tb.h" + +/* port utility functions */ + +static const char *tb_port_type(struct tb_regs_port_header *port) +{ + switch (port->type >> 16) { + case 0: + switch ((u8) port->type) { + case 0: + return "Inactive"; + case 1: + return "Port"; + case 2: + return "NHI"; + default: + return "unknown"; + } + case 0x2: + return "Ethernet"; + case 0x8: + return "SATA"; + case 0xe: + return "DP/HDMI"; + case 0x10: + return "PCIe"; + case 0x20: + return "USB"; + default: + return "unknown"; + } +} + +static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) +{ + tb_info(tb, + " Port %d: %x:%x (Revision: %d, TB Version: %d, Type: %s (%#x))\n", + port->port_number, port->vendor_id, port->device_id, + port->revision, port->thunderbolt_version, tb_port_type(port), + port->type); + tb_info(tb, " Max hop id (in/out): %d/%d\n", + port->max_in_hop_id, port->max_out_hop_id); + tb_info(tb, " Max counters: %d\n", port->max_counters); + tb_info(tb, " NFC Credits: %#x\n", port->nfc_credits); +} + +/** + * tb_init_port() - initialize a port + * + * This is a helper method for tb_switch_alloc. Does not check or initialize + * any downstream switches. + * + * Return: Returns 0 on success or an error code on failure. + */ +static int tb_init_port(struct tb_switch *sw, u8 port_nr) +{ + int res; + struct tb_port *port = &sw->ports[port_nr]; + port->sw = sw; + port->port = port_nr; + port->remote = NULL; + res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8); + if (res) + return res; + + tb_dump_port(sw->tb, &port->config); + + /* TODO: Read dual link port, DP port and more from EEPROM. */ + return 0; + +} + +/* switch utility functions */ + +static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) +{ + tb_info(tb, + " Switch: %x:%x (Revision: %d, TB Version: %d)\n", + sw->vendor_id, sw->device_id, sw->revision, + sw->thunderbolt_version); + tb_info(tb, " Max Port Number: %d\n", sw->max_port_number); + tb_info(tb, " Config:\n"); + tb_info(tb, + " Upstream Port Number: %d Depth: %d Route String: %#llx Enabled: %d, PlugEventsDelay: %dms\n", + sw->upstream_port_number, sw->depth, + (((u64) sw->route_hi) << 32) | sw->route_lo, + sw->enabled, sw->plug_events_delay); + tb_info(tb, + " unknown1: %#x unknown4: %#x\n", + sw->__unknown1, sw->__unknown4); +} + +/** + * tb_switch_free() - free a tb_switch and all downstream switches + */ +void tb_switch_free(struct tb_switch *sw) +{ + int i; + /* port 0 is the switch itself and never has a remote */ + for (i = 1; i <= sw->config.max_port_number; i++) { + if (tb_is_upstream_port(&sw->ports[i])) + continue; + if (sw->ports[i].remote) + tb_switch_free(sw->ports[i].remote->sw); + sw->ports[i].remote = NULL; + } + + kfree(sw->ports); + kfree(sw); +} + +/** + * tb_switch_alloc() - allocate and initialize a switch + * + * Return: Returns a NULL on failure. + */ +struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) +{ + int i; + struct tb_switch *sw; + int upstream_port = tb_cfg_get_upstream_port(tb->ctl, route); + if (upstream_port < 0) + return NULL; + + sw = kzalloc(sizeof(*sw), GFP_KERNEL); + if (!sw) + return NULL; + + sw->tb = tb; + if (tb_cfg_read(tb->ctl, &sw->config, route, 0, 2, 0, 5)) + goto err; + tb_info(tb, + "initializing Switch at %#llx (depth: %d, up port: %d)\n", + route, tb_route_length(route), upstream_port); + tb_info(tb, "old switch config:\n"); + tb_dump_switch(tb, &sw->config); + + /* configure switch */ + sw->config.upstream_port_number = upstream_port; + sw->config.depth = tb_route_length(route); + sw->config.route_lo = route; + sw->config.route_hi = route >> 32; + sw->config.enabled = 1; + /* from here on we may use the tb_sw_* functions & macros */ + + if (sw->config.vendor_id != 0x8086) + tb_sw_warn(sw, "unknown switch vendor id %#x\n", + sw->config.vendor_id); + + if (sw->config.device_id != 0x1547 && sw->config.device_id != 0x1549) + tb_sw_warn(sw, "unsupported switch device id %#x\n", + sw->config.device_id); + + /* upload configuration */ + if (tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3)) + goto err; + + /* initialize ports */ + sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), + GFP_KERNEL); + if (!sw->ports) + goto err; + + for (i = 0; i <= sw->config.max_port_number; i++) { + if (tb_init_port(sw, i)) + goto err; + /* TODO: check if port is disabled (EEPROM) */ + } + + /* TODO: I2C, IECS, EEPROM, link controller */ + + return sw; +err: + kfree(sw->ports); + kfree(sw); + return NULL; +} + diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 164dea083e9e..f1b6100b6cf0 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -71,6 +71,10 @@ void thunderbolt_shutdown_and_free(struct tb *tb) { mutex_lock(&tb->lock); + if (tb->root_switch) + tb_switch_free(tb->root_switch); + tb->root_switch = NULL; + if (tb->ctl) { tb_ctl_stop(tb->ctl); tb_ctl_free(tb->ctl); @@ -126,6 +130,10 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) */ tb_ctl_start(tb->ctl); + tb->root_switch = tb_switch_alloc(tb, 0); + if (!tb->root_switch) + goto err_locked; + /* Allow tb_handle_hotplug to progress events */ tb->hotplug_active = true; mutex_unlock(&tb->lock); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 6055dfd713fc..389dbb405c5f 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -7,8 +7,30 @@ #ifndef TB_H_ #define TB_H_ +#include + +#include "tb_regs.h" #include "ctl.h" +/** + * struct tb_switch - a thunderbolt switch + */ +struct tb_switch { + struct tb_regs_switch_header config; + struct tb_port *ports; + struct tb *tb; +}; + +/** + * struct tb_port - a thunderbolt port, part of a tb_switch + */ +struct tb_port { + struct tb_regs_port_header config; + struct tb_switch *sw; + struct tb_port *remote; /* remote port, NULL if not connected */ + u8 port; /* port number on switch */ +}; + /** * struct tb - main thunderbolt bus structure */ @@ -20,6 +42,7 @@ struct tb { struct tb_nhi *nhi; struct tb_ctl *ctl; struct workqueue_struct *wq; /* ordered workqueue for plug events */ + struct tb_switch *root_switch; bool hotplug_active; /* * tb_handle_hotplug will stop progressing plug * events and exit if this is not set (it needs to @@ -29,7 +52,122 @@ struct tb { }; +/* helper functions & macros */ + +/** + * tb_upstream_port() - return the upstream port of a switch + * + * Every switch has an upstream port (for the root switch it is the NHI). + * + * During switch alloc/init tb_upstream_port()->remote may be NULL, even for + * non root switches (on the NHI port remote is always NULL). + * + * Return: Returns the upstream port of the switch. + */ +static inline struct tb_port *tb_upstream_port(struct tb_switch *sw) +{ + return &sw->ports[sw->config.upstream_port_number]; +} + +static inline u64 tb_route(struct tb_switch *sw) +{ + return ((u64) sw->config.route_hi) << 32 | sw->config.route_lo; +} + +static inline int tb_sw_read(struct tb_switch *sw, void *buffer, + enum tb_cfg_space space, u32 offset, u32 length) +{ + return tb_cfg_read(sw->tb->ctl, + buffer, + tb_route(sw), + 0, + space, + offset, + length); +} + +static inline int tb_sw_write(struct tb_switch *sw, void *buffer, + enum tb_cfg_space space, u32 offset, u32 length) +{ + return tb_cfg_write(sw->tb->ctl, + buffer, + tb_route(sw), + 0, + space, + offset, + length); +} + +static inline int tb_port_read(struct tb_port *port, void *buffer, + enum tb_cfg_space space, u32 offset, u32 length) +{ + return tb_cfg_read(port->sw->tb->ctl, + buffer, + tb_route(port->sw), + port->port, + space, + offset, + length); +} + +static inline int tb_port_write(struct tb_port *port, void *buffer, + enum tb_cfg_space space, u32 offset, u32 length) +{ + return tb_cfg_write(port->sw->tb->ctl, + buffer, + tb_route(port->sw), + port->port, + space, + offset, + length); +} + +#define tb_err(tb, fmt, arg...) dev_err(&(tb)->nhi->pdev->dev, fmt, ## arg) +#define tb_WARN(tb, fmt, arg...) dev_WARN(&(tb)->nhi->pdev->dev, fmt, ## arg) +#define tb_warn(tb, fmt, arg...) dev_warn(&(tb)->nhi->pdev->dev, fmt, ## arg) +#define tb_info(tb, fmt, arg...) dev_info(&(tb)->nhi->pdev->dev, fmt, ## arg) + + +#define __TB_SW_PRINT(level, sw, fmt, arg...) \ + do { \ + struct tb_switch *__sw = (sw); \ + level(__sw->tb, "%llx: " fmt, \ + tb_route(__sw), ## arg); \ + } while (0) +#define tb_sw_WARN(sw, fmt, arg...) __TB_SW_PRINT(tb_WARN, sw, fmt, ##arg) +#define tb_sw_warn(sw, fmt, arg...) __TB_SW_PRINT(tb_warn, sw, fmt, ##arg) +#define tb_sw_info(sw, fmt, arg...) __TB_SW_PRINT(tb_info, sw, fmt, ##arg) + + +#define __TB_PORT_PRINT(level, _port, fmt, arg...) \ + do { \ + struct tb_port *__port = (_port); \ + level(__port->sw->tb, "%llx:%x: " fmt, \ + tb_route(__port->sw), __port->port, ## arg); \ + } while (0) +#define tb_port_WARN(port, fmt, arg...) \ + __TB_PORT_PRINT(tb_WARN, port, fmt, ##arg) +#define tb_port_warn(port, fmt, arg...) \ + __TB_PORT_PRINT(tb_warn, port, fmt, ##arg) +#define tb_port_info(port, fmt, arg...) \ + __TB_PORT_PRINT(tb_info, port, fmt, ##arg) + + struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); void thunderbolt_shutdown_and_free(struct tb *tb); +struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); +void tb_switch_free(struct tb_switch *sw); + + +static inline int tb_route_length(u64 route) +{ + return (fls64(route) + TB_ROUTE_SHIFT - 1) / TB_ROUTE_SHIFT; +} + +static inline bool tb_is_upstream_port(struct tb_port *port) +{ + return port == tb_upstream_port(port->sw); +} + #endif -- cgit v1.2.3 From e2b8785ed312dad3a18279a3e88435fc269658c1 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:03 +0200 Subject: thunderbolt: Add thunderbolt capability handling Thunderbolt config areas contain capability lists similar to those found on pci devices. This patch introduces a tb_find_cap utility method to search for capabilities. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/cap.c | 116 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 2 + 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/cap.c diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 4ac18d969306..617b31480b2e 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o diff --git a/drivers/thunderbolt/cap.c b/drivers/thunderbolt/cap.c new file mode 100644 index 000000000000..a7b47e7cddbd --- /dev/null +++ b/drivers/thunderbolt/cap.c @@ -0,0 +1,116 @@ +/* + * Thunderbolt Cactus Ridge driver - capabilities lookup + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include + +#include "tb.h" + + +struct tb_cap_any { + union { + struct tb_cap_basic basic; + struct tb_cap_extended_short extended_short; + struct tb_cap_extended_long extended_long; + }; +} __packed; + +static bool tb_cap_is_basic(struct tb_cap_any *cap) +{ + /* basic.cap is u8. This checks only the lower 8 bit of cap. */ + return cap->basic.cap != 5; +} + +static bool tb_cap_is_long(struct tb_cap_any *cap) +{ + return !tb_cap_is_basic(cap) + && cap->extended_short.next == 0 + && cap->extended_short.length == 0; +} + +static enum tb_cap tb_cap(struct tb_cap_any *cap) +{ + if (tb_cap_is_basic(cap)) + return cap->basic.cap; + else + /* extended_short/long have cap at the same offset. */ + return cap->extended_short.cap; +} + +static u32 tb_cap_next(struct tb_cap_any *cap, u32 offset) +{ + int next; + if (offset == 1) { + /* + * The first pointer is part of the switch header and always + * a simple pointer. + */ + next = cap->basic.next; + } else { + /* + * Somehow Intel decided to use 3 different types of capability + * headers. It is not like anyone could have predicted that + * single byte offsets are not enough... + */ + if (tb_cap_is_basic(cap)) + next = cap->basic.next; + else if (!tb_cap_is_long(cap)) + next = cap->extended_short.next; + else + next = cap->extended_long.next; + } + /* + * "Hey, we could terminate some capability lists with a null offset + * and others with a pointer to the last element." - "Great idea!" + */ + if (next == offset) + return 0; + return next; +} + +/** + * tb_find_cap() - find a capability + * + * Return: Returns a positive offset if the capability was found and 0 if not. + * Returns an error code on failure. + */ +int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap) +{ + u32 offset = 1; + struct tb_cap_any header; + int res; + int retries = 10; + while (retries--) { + res = tb_port_read(port, &header, space, offset, 1); + if (res) { + /* Intel needs some help with linked lists. */ + if (space == TB_CFG_PORT && offset == 0xa + && port->config.type == TB_TYPE_DP_HDMI_OUT) { + offset = 0x39; + continue; + } + return res; + } + if (offset != 1) { + if (tb_cap(&header) == cap) + return offset; + if (tb_cap_is_long(&header)) { + /* tb_cap_extended_long is 2 dwords */ + res = tb_port_read(port, &header, space, + offset, 2); + if (res) + return res; + } + } + offset = tb_cap_next(&header, offset); + if (!offset) + return 0; + } + tb_port_WARN(port, + "run out of retries while looking for cap %#x in config space %d, last offset: %#x\n", + cap, space, offset); + return -EIO; +} diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 389dbb405c5f..38e4c23c10fd 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -159,6 +159,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); + static inline int tb_route_length(u64 route) { -- cgit v1.2.3 From ca389f716f6140d5349583a716bc629d63b06b1f Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:04 +0200 Subject: thunderbolt: Enable plug events Thunderbolt switches have a plug events capability. This patch adds the tb_plug_events_active method and uses it to activate plug events during switch allocation. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 52 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + 2 files changed, 53 insertions(+) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index e89121ffe44e..6d193a230cf4 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -98,6 +98,45 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +/** + * tb_plug_events_active() - enable/disable plug events on a switch + * + * Also configures a sane plug_events_delay of 255ms. + * + * Return: Returns 0 on success or an error code on failure. + */ +static int tb_plug_events_active(struct tb_switch *sw, bool active) +{ + u32 data; + int res; + + sw->config.plug_events_delay = 0xff; + res = tb_sw_write(sw, ((u32 *) &sw->config) + 4, TB_CFG_SWITCH, 4, 1); + if (res) + return res; + + res = tb_sw_read(sw, &data, TB_CFG_SWITCH, sw->cap_plug_events + 1, 1); + if (res) + return res; + + if (active) { + data = data & 0xFFFFFF83; + switch (sw->config.device_id) { + case 0x1513: + case 0x151a: + case 0x1549: + break; + default: + data |= 4; + } + } else { + data = data | 0x7c; + } + return tb_sw_write(sw, &data, TB_CFG_SWITCH, + sw->cap_plug_events + 1, 1); +} + + /** * tb_switch_free() - free a tb_switch and all downstream switches */ @@ -113,6 +152,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote = NULL; } + tb_plug_events_active(sw, false); + kfree(sw->ports); kfree(sw); } @@ -125,6 +166,7 @@ void tb_switch_free(struct tb_switch *sw) struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) { int i; + int cap; struct tb_switch *sw; int upstream_port = tb_cfg_get_upstream_port(tb->ctl, route); if (upstream_port < 0) @@ -177,6 +219,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) /* TODO: I2C, IECS, EEPROM, link controller */ + cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS); + if (cap < 0) { + tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n"); + goto err; + } + sw->cap_plug_events = cap; + + if (tb_plug_events_active(sw, true)) + goto err; + return sw; err: kfree(sw->ports); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 38e4c23c10fd..af123c4045e3 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -19,6 +19,7 @@ struct tb_switch { struct tb_regs_switch_header config; struct tb_port *ports; struct tb *tb; + int cap_plug_events; /* offset, zero if not found */ }; /** -- cgit v1.2.3 From 9da672a42878c58af5c50d7389dbae17bea9df38 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:05 +0200 Subject: thunderbolt: Scan for downstream switches Add utility methods tb_port_state and tb_wait_for_port. Add tb_scan_switch which recursively checks for downstream switches. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 97 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 44 ++++++++++++++++++++ drivers/thunderbolt/tb.h | 16 ++++++++ 3 files changed, 157 insertions(+) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 6d193a230cf4..b31b8cef301d 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -52,6 +52,92 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) tb_info(tb, " NFC Credits: %#x\n", port->nfc_credits); } +/** + * tb_port_state() - get connectedness state of a port + * + * The port must have a TB_CAP_PHY (i.e. it should be a real port). + * + * Return: Returns an enum tb_port_state on success or an error code on failure. + */ +static int tb_port_state(struct tb_port *port) +{ + struct tb_cap_phy phy; + int res; + if (port->cap_phy == 0) { + tb_port_WARN(port, "does not have a PHY\n"); + return -EINVAL; + } + res = tb_port_read(port, &phy, TB_CFG_PORT, port->cap_phy, 2); + if (res) + return res; + return phy.state; +} + +/** + * tb_wait_for_port() - wait for a port to become ready + * + * Wait up to 1 second for a port to reach state TB_PORT_UP. If + * wait_if_unplugged is set then we also wait if the port is in state + * TB_PORT_UNPLUGGED (it takes a while for the device to be registered after + * switch resume). Otherwise we only wait if a device is registered but the link + * has not yet been established. + * + * Return: Returns an error code on failure. Returns 0 if the port is not + * connected or failed to reach state TB_PORT_UP within one second. Returns 1 + * if the port is connected and in state TB_PORT_UP. + */ +int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) +{ + int retries = 10; + int state; + if (!port->cap_phy) { + tb_port_WARN(port, "does not have PHY\n"); + return -EINVAL; + } + if (tb_is_upstream_port(port)) { + tb_port_WARN(port, "is the upstream port\n"); + return -EINVAL; + } + + while (retries--) { + state = tb_port_state(port); + if (state < 0) + return state; + if (state == TB_PORT_DISABLED) { + tb_port_info(port, "is disabled (state: 0)\n"); + return 0; + } + if (state == TB_PORT_UNPLUGGED) { + if (wait_if_unplugged) { + /* used during resume */ + tb_port_info(port, + "is unplugged (state: 7), retrying...\n"); + msleep(100); + continue; + } + tb_port_info(port, "is unplugged (state: 7)\n"); + return 0; + } + if (state == TB_PORT_UP) { + tb_port_info(port, + "is connected, link is up (state: 2)\n"); + return 1; + } + + /* + * After plug-in the state is TB_PORT_CONNECTING. Give it some + * time. + */ + tb_port_info(port, + "is connected, link is not up (state: %d), retrying...\n", + state); + msleep(100); + } + tb_port_warn(port, + "failed to reach state TB_PORT_UP. Ignoring port...\n"); + return 0; +} + /** * tb_init_port() - initialize a port * @@ -63,6 +149,7 @@ static void tb_dump_port(struct tb *tb, struct tb_regs_port_header *port) static int tb_init_port(struct tb_switch *sw, u8 port_nr) { int res; + int cap; struct tb_port *port = &sw->ports[port_nr]; port->sw = sw; port->port = port_nr; @@ -71,6 +158,16 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr) if (res) return res; + /* Port 0 is the switch itself and has no PHY. */ + if (port->config.type == TB_TYPE_PORT && port_nr != 0) { + cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY); + + if (cap > 0) + port->cap_phy = cap; + else + tb_port_WARN(port, "non switch port without a PHY\n"); + } + tb_dump_port(sw->tb, &port->config); /* TODO: Read dual link port, DP port and more from EEPROM. */ diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index f1b6100b6cf0..3b716fd123f6 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -11,6 +11,47 @@ #include "tb.h" #include "tb_regs.h" + +/* enumeration & hot plug handling */ + + +static void tb_scan_port(struct tb_port *port); + +/** + * tb_scan_switch() - scan for and initialize downstream switches + */ +static void tb_scan_switch(struct tb_switch *sw) +{ + int i; + for (i = 1; i <= sw->config.max_port_number; i++) + tb_scan_port(&sw->ports[i]); +} + +/** + * tb_scan_port() - check for and initialize switches below port + */ +static void tb_scan_port(struct tb_port *port) +{ + struct tb_switch *sw; + if (tb_is_upstream_port(port)) + return; + if (port->config.type != TB_TYPE_PORT) + return; + if (tb_wait_for_port(port, false) <= 0) + return; + if (port->remote) { + tb_port_WARN(port, "port already has a remote!\n"); + return; + } + sw = tb_switch_alloc(port->sw->tb, tb_downstream_route(port)); + if (!sw) + return; + port->remote = tb_upstream_port(sw); + tb_upstream_port(sw)->remote = port; + tb_scan_switch(sw); +} + + /* hotplug handling */ struct tb_hotplug_event { @@ -134,6 +175,9 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) if (!tb->root_switch) goto err_locked; + /* Full scan to discover devices added before the driver was loaded. */ + tb_scan_switch(tb->root_switch); + /* Allow tb_handle_hotplug to progress events */ tb->hotplug_active = true; mutex_unlock(&tb->lock); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index af123c4045e3..70a66fef0177 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -29,6 +29,7 @@ struct tb_port { struct tb_regs_port_header config; struct tb_switch *sw; struct tb_port *remote; /* remote port, NULL if not connected */ + int cap_phy; /* offset, zero if not found */ u8 port; /* port number on switch */ }; @@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); + int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); @@ -173,4 +176,17 @@ static inline bool tb_is_upstream_port(struct tb_port *port) return port == tb_upstream_port(port->sw); } +/** + * tb_downstream_route() - get route to downstream switch + * + * Port must not be the upstream port (otherwise a loop is created). + * + * Return: Returns a route to the switch behind @port. + */ +static inline u64 tb_downstream_route(struct tb_port *port) +{ + return tb_route(port->sw) + | ((u64) port->port << (port->sw->config.depth * 8)); +} + #endif -- cgit v1.2.3 From 053596d9e26c86352c4b2b372f43f2746b97de45 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:06 +0200 Subject: thunderbolt: Handle hotplug events We receive a plug event callback whenever a thunderbolt device is added or removed. This patch fills in the tb_handle_hotplug method and starts reacting to these events by adding/removing switches from the hierarchy. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.c | 46 +++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/tb.h | 3 +++ 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index b31b8cef301d..d6c32e1e2b42 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -195,6 +195,24 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) +{ + u8 next_port = route; /* + * Routes use a stride of 8 bits, + * eventhough a port index has 6 bits at most. + * */ + if (route == 0) + return sw; + if (next_port > sw->config.max_port_number) + return 0; + if (tb_is_upstream_port(&sw->ports[next_port])) + return 0; + if (!sw->ports[next_port].remote) + return 0; + return get_switch_at_route(sw->ports[next_port].remote->sw, + route >> TB_ROUTE_SHIFT); +} + /** * tb_plug_events_active() - enable/disable plug events on a switch * @@ -249,7 +267,8 @@ void tb_switch_free(struct tb_switch *sw) sw->ports[i].remote = NULL; } - tb_plug_events_active(sw, false); + if (!sw->is_unplugged) + tb_plug_events_active(sw, false); kfree(sw->ports); kfree(sw); @@ -333,3 +352,24 @@ err: return NULL; } +/** + * tb_sw_set_unpplugged() - set is_unplugged on switch and downstream switches + */ +void tb_sw_set_unpplugged(struct tb_switch *sw) +{ + int i; + if (sw == sw->tb->root_switch) { + tb_sw_WARN(sw, "cannot unplug root switch\n"); + return; + } + if (sw->is_unplugged) { + tb_sw_WARN(sw, "is_unplugged already set\n"); + return; + } + sw->is_unplugged = true; + for (i = 0; i <= sw->config.max_port_number; i++) { + if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) + tb_sw_set_unpplugged(sw->ports[i].remote->sw); + } +} + diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 3b716fd123f6..1efcacc72104 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -71,11 +71,55 @@ static void tb_handle_hotplug(struct work_struct *work) { struct tb_hotplug_event *ev = container_of(work, typeof(*ev), work); struct tb *tb = ev->tb; + struct tb_switch *sw; + struct tb_port *port; mutex_lock(&tb->lock); if (!tb->hotplug_active) goto out; /* during init, suspend or shutdown */ - /* do nothing for now */ + sw = get_switch_at_route(tb->root_switch, ev->route); + if (!sw) { + tb_warn(tb, + "hotplug event from non existent switch %llx:%x (unplug: %d)\n", + ev->route, ev->port, ev->unplug); + goto out; + } + if (ev->port > sw->config.max_port_number) { + tb_warn(tb, + "hotplug event from non existent port %llx:%x (unplug: %d)\n", + ev->route, ev->port, ev->unplug); + goto out; + } + port = &sw->ports[ev->port]; + if (tb_is_upstream_port(port)) { + tb_warn(tb, + "hotplug event for upstream port %llx:%x (unplug: %d)\n", + ev->route, ev->port, ev->unplug); + goto out; + } + if (ev->unplug) { + if (port->remote) { + tb_port_info(port, "unplugged\n"); + tb_sw_set_unpplugged(port->remote->sw); + tb_switch_free(port->remote->sw); + port->remote = NULL; + } else { + tb_port_info(port, + "got unplug event for disconnected port, ignoring\n"); + } + } else if (port->remote) { + tb_port_info(port, + "got plug event for connected port, ignoring\n"); + } else { + tb_port_info(port, "hotplug: scanning\n"); + tb_scan_port(port); + if (!port->remote) { + tb_port_info(port, "hotplug: no switch found\n"); + } else if (port->remote->sw->config.depth > 1) { + tb_sw_warn(port->remote->sw, + "hotplug: chaining not supported\n"); + } + } out: mutex_unlock(&tb->lock); kfree(ev); diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 70a66fef0177..661f1828527a 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -20,6 +20,7 @@ struct tb_switch { struct tb_port *ports; struct tb *tb; int cap_plug_events; /* offset, zero if not found */ + bool is_unplugged; /* unplugged, will go away */ }; /** @@ -160,6 +161,8 @@ void thunderbolt_shutdown_and_free(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +void tb_sw_set_unpplugged(struct tb_switch *sw); +struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); -- cgit v1.2.3 From 520b670216a15fb949e6ec6a1af9b5dd55d219c7 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:07 +0200 Subject: thunderbolt: Add path setup code. A thunderbolt path is a unidirectional channel between two thunderbolt ports. Two such paths are needed to establish a pci tunnel. This patch introduces struct tb_path as well as a set of tb_path_* methods which are used to activate & deactivate paths. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/path.c | 215 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 34 +++++++ drivers/thunderbolt/tb.h | 62 +++++++++++++ 4 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/path.c diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 617b31480b2e..3532f3684efc 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o diff --git a/drivers/thunderbolt/path.c b/drivers/thunderbolt/path.c new file mode 100644 index 000000000000..8fcf8a7b6c22 --- /dev/null +++ b/drivers/thunderbolt/path.c @@ -0,0 +1,215 @@ +/* + * Thunderbolt Cactus Ridge driver - path/tunnel functionality + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include + +#include "tb.h" + + +static void tb_dump_hop(struct tb_port *port, struct tb_regs_hop *hop) +{ + tb_port_info(port, " Hop through port %d to hop %d (%s)\n", + hop->out_port, hop->next_hop, + hop->enable ? "enabled" : "disabled"); + tb_port_info(port, " Weight: %d Priority: %d Credits: %d Drop: %d\n", + hop->weight, hop->priority, + hop->initial_credits, hop->drop_packages); + tb_port_info(port, " Counter enabled: %d Counter index: %d\n", + hop->counter_enable, hop->counter); + tb_port_info(port, " Flow Control (In/Eg): %d/%d Shared Buffer (In/Eg): %d/%d\n", + hop->ingress_fc, hop->egress_fc, + hop->ingress_shared_buffer, hop->egress_shared_buffer); + tb_port_info(port, " Unknown1: %#x Unknown2: %#x Unknown3: %#x\n", + hop->unknown1, hop->unknown2, hop->unknown3); +} + +/** + * tb_path_alloc() - allocate a thunderbolt path + * + * Return: Returns a tb_path on success or NULL on failure. + */ +struct tb_path *tb_path_alloc(struct tb *tb, int num_hops) +{ + struct tb_path *path = kzalloc(sizeof(*path), GFP_KERNEL); + if (!path) + return NULL; + path->hops = kcalloc(num_hops, sizeof(*path->hops), GFP_KERNEL); + if (!path->hops) { + kfree(path); + return NULL; + } + path->tb = tb; + path->path_length = num_hops; + return path; +} + +/** + * tb_path_free() - free a deactivated path + */ +void tb_path_free(struct tb_path *path) +{ + if (path->activated) { + tb_WARN(path->tb, "trying to free an activated path\n") + return; + } + kfree(path->hops); + kfree(path); +} + +static void __tb_path_deallocate_nfc(struct tb_path *path, int first_hop) +{ + int i, res; + for (i = first_hop; i < path->path_length; i++) { + res = tb_port_add_nfc_credits(path->hops[i].in_port, + -path->nfc_credits); + if (res) + tb_port_warn(path->hops[i].in_port, + "nfc credits deallocation failed for hop %d\n", + i); + } +} + +static void __tb_path_deactivate_hops(struct tb_path *path, int first_hop) +{ + int i, res; + struct tb_regs_hop hop = { }; + for (i = first_hop; i < path->path_length; i++) { + res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, + 2 * path->hops[i].in_hop_index, 2); + if (res) + tb_port_warn(path->hops[i].in_port, + "hop deactivation failed for hop %d, index %d\n", + i, path->hops[i].in_hop_index); + } +} + +void tb_path_deactivate(struct tb_path *path) +{ + if (!path->activated) { + tb_WARN(path->tb, "trying to deactivate an inactive path\n"); + return; + } + tb_info(path->tb, + "deactivating path from %llx:%x to %llx:%x\n", + tb_route(path->hops[0].in_port->sw), + path->hops[0].in_port->port, + tb_route(path->hops[path->path_length - 1].out_port->sw), + path->hops[path->path_length - 1].out_port->port); + __tb_path_deactivate_hops(path, 0); + __tb_path_deallocate_nfc(path, 0); + path->activated = false; +} + +/** + * tb_path_activate() - activate a path + * + * Activate a path starting with the last hop and iterating backwards. The + * caller must fill path->hops before calling tb_path_activate(). + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_path_activate(struct tb_path *path) +{ + int i, res; + enum tb_path_port out_mask, in_mask; + if (path->activated) { + tb_WARN(path->tb, "trying to activate already activated path\n"); + return -EINVAL; + } + + tb_info(path->tb, + "activating path from %llx:%x to %llx:%x\n", + tb_route(path->hops[0].in_port->sw), + path->hops[0].in_port->port, + tb_route(path->hops[path->path_length - 1].out_port->sw), + path->hops[path->path_length - 1].out_port->port); + + /* Clear counters. */ + for (i = path->path_length - 1; i >= 0; i--) { + if (path->hops[i].in_counter_index == -1) + continue; + res = tb_port_clear_counter(path->hops[i].in_port, + path->hops[i].in_counter_index); + if (res) + goto err; + } + + /* Add non flow controlled credits. */ + for (i = path->path_length - 1; i >= 0; i--) { + res = tb_port_add_nfc_credits(path->hops[i].in_port, + path->nfc_credits); + if (res) { + __tb_path_deallocate_nfc(path, i); + goto err; + } + } + + /* Activate hops. */ + for (i = path->path_length - 1; i >= 0; i--) { + struct tb_regs_hop hop; + + /* dword 0 */ + hop.next_hop = path->hops[i].next_hop_index; + hop.out_port = path->hops[i].out_port->port; + /* TODO: figure out why these are good values */ + hop.initial_credits = (i == path->path_length - 1) ? 16 : 7; + hop.unknown1 = 0; + hop.enable = 1; + + /* dword 1 */ + out_mask = (i == path->path_length - 1) ? + TB_PATH_DESTINATION : TB_PATH_INTERNAL; + in_mask = (i == 0) ? TB_PATH_SOURCE : TB_PATH_INTERNAL; + hop.weight = path->weight; + hop.unknown2 = 0; + hop.priority = path->priority; + hop.drop_packages = path->drop_packages; + hop.counter = path->hops[i].in_counter_index; + hop.counter_enable = path->hops[i].in_counter_index != -1; + hop.ingress_fc = path->ingress_fc_enable & in_mask; + hop.egress_fc = path->egress_fc_enable & out_mask; + hop.ingress_shared_buffer = path->ingress_shared_buffer + & in_mask; + hop.egress_shared_buffer = path->egress_shared_buffer + & out_mask; + hop.unknown3 = 0; + + tb_port_info(path->hops[i].in_port, "Writing hop %d, index %d", + i, path->hops[i].in_hop_index); + tb_dump_hop(path->hops[i].in_port, &hop); + res = tb_port_write(path->hops[i].in_port, &hop, TB_CFG_HOPS, + 2 * path->hops[i].in_hop_index, 2); + if (res) { + __tb_path_deactivate_hops(path, i); + __tb_path_deallocate_nfc(path, 0); + goto err; + } + } + path->activated = true; + tb_info(path->tb, "path activation complete\n"); + return 0; +err: + tb_WARN(path->tb, "path activation failed\n"); + return res; +} + +/** + * tb_path_is_invalid() - check whether any ports on the path are invalid + * + * Return: Returns true if the path is invalid, false otherwise. + */ +bool tb_path_is_invalid(struct tb_path *path) +{ + int i = 0; + for (i = 0; i < path->path_length; i++) { + if (path->hops[i].in_port->sw->is_unplugged) + return true; + if (path->hops[i].out_port->sw->is_unplugged) + return true; + } + return false; +} diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index d6c32e1e2b42..667413f3ad7a 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -138,6 +138,40 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged) return 0; } +/** + * tb_port_add_nfc_credits() - add/remove non flow controlled credits to port + * + * Change the number of NFC credits allocated to @port by @credits. To remove + * NFC credits pass a negative amount of credits. + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_port_add_nfc_credits(struct tb_port *port, int credits) +{ + if (credits == 0) + return 0; + tb_port_info(port, + "adding %#x NFC credits (%#x -> %#x)", + credits, + port->config.nfc_credits, + port->config.nfc_credits + credits); + port->config.nfc_credits += credits; + return tb_port_write(port, &port->config.nfc_credits, + TB_CFG_PORT, 4, 1); +} + +/** + * tb_port_clear_counter() - clear a counter in TB_CFG_COUNTER + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_port_clear_counter(struct tb_port *port, int counter) +{ + u32 zero[3] = { 0, 0, 0 }; + tb_port_info(port, "clearing counter %d\n", counter); + return tb_port_write(port, zero, TB_CFG_COUNTERS, 3 * counter, 3); +} + /** * tb_init_port() - initialize a port * diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 661f1828527a..8bbdc2bc4d09 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -34,6 +34,60 @@ struct tb_port { u8 port; /* port number on switch */ }; +/** + * struct tb_path_hop - routing information for a tb_path + * + * Hop configuration is always done on the IN port of a switch. + * in_port and out_port have to be on the same switch. Packets arriving on + * in_port with "hop" = in_hop_index will get routed to through out_port. The + * next hop to take (on out_port->remote) is determined by next_hop_index. + * + * in_counter_index is the index of a counter (in TB_CFG_COUNTERS) on the in + * port. + */ +struct tb_path_hop { + struct tb_port *in_port; + struct tb_port *out_port; + int in_hop_index; + int in_counter_index; /* write -1 to disable counters for this hop. */ + int next_hop_index; +}; + +/** + * enum tb_path_port - path options mask + */ +enum tb_path_port { + TB_PATH_NONE = 0, + TB_PATH_SOURCE = 1, /* activate on the first hop (out of src) */ + TB_PATH_INTERNAL = 2, /* activate on other hops (not the first/last) */ + TB_PATH_DESTINATION = 4, /* activate on the last hop (into dst) */ + TB_PATH_ALL = 7, +}; + +/** + * struct tb_path - a unidirectional path between two ports + * + * A path consists of a number of hops (see tb_path_hop). To establish a PCIe + * tunnel two paths have to be created between the two PCIe ports. + * + */ +struct tb_path { + struct tb *tb; + int nfc_credits; /* non flow controlled credits */ + enum tb_path_port ingress_shared_buffer; + enum tb_path_port egress_shared_buffer; + enum tb_path_port ingress_fc_enable; + enum tb_path_port egress_fc_enable; + + int priority:3; + int weight:4; + bool drop_packages; + bool activated; + struct tb_path_hop *hops; + int path_length; /* number of hops */ +}; + + /** * struct tb - main thunderbolt bus structure */ @@ -165,9 +219,17 @@ void tb_sw_set_unpplugged(struct tb_switch *sw); struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); +int tb_port_add_nfc_credits(struct tb_port *port, int credits); +int tb_port_clear_counter(struct tb_port *port, int counter); int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); +struct tb_path *tb_path_alloc(struct tb *tb, int num_hops); +void tb_path_free(struct tb_path *path); +int tb_path_activate(struct tb_path *path); +void tb_path_deactivate(struct tb_path *path); +bool tb_path_is_invalid(struct tb_path *path); + static inline int tb_route_length(u64 route) { -- cgit v1.2.3 From 3364f0c12795713e89ae1209081c60d64bfb4ca1 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:08 +0200 Subject: thunderbolt: Add support for simple pci tunnels A pci downstream and pci upstream port can be connected through a tunnel. To establish the tunnel we have to setup two unidirectional paths between the two ports. Right now we only support paths with two hops (i.e. no chaining) and at most one pci device per thunderbolt device. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/tb.c | 135 +++++++++++++++++++++++ drivers/thunderbolt/tb.h | 1 + drivers/thunderbolt/tunnel_pci.c | 232 +++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tunnel_pci.h | 30 +++++ 5 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/tunnel_pci.c create mode 100644 drivers/thunderbolt/tunnel_pci.h diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 3532f3684efc..0122ca698f78 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 1efcacc72104..177f61df464d 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -10,6 +10,7 @@ #include "tb.h" #include "tb_regs.h" +#include "tunnel_pci.h" /* enumeration & hot plug handling */ @@ -51,6 +52,124 @@ static void tb_scan_port(struct tb_port *port) tb_scan_switch(sw); } +/** + * tb_free_invalid_tunnels() - destroy tunnels of devices that have gone away + */ +static void tb_free_invalid_tunnels(struct tb *tb) +{ + struct tb_pci_tunnel *tunnel; + struct tb_pci_tunnel *n; + list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) + { + if (tb_pci_is_invalid(tunnel)) { + tb_pci_deactivate(tunnel); + tb_pci_free(tunnel); + } + } +} + +/** + * find_pci_up_port() - return the first PCIe up port on @sw or NULL + */ +static struct tb_port *tb_find_pci_up_port(struct tb_switch *sw) +{ + int i; + for (i = 1; i <= sw->config.max_port_number; i++) + if (sw->ports[i].config.type == TB_TYPE_PCIE_UP) + return &sw->ports[i]; + return NULL; +} + +/** + * find_unused_down_port() - return the first inactive PCIe down port on @sw + */ +static struct tb_port *tb_find_unused_down_port(struct tb_switch *sw) +{ + int i; + int cap; + int res; + int data; + for (i = 1; i <= sw->config.max_port_number; i++) { + if (tb_is_upstream_port(&sw->ports[i])) + continue; + if (sw->ports[i].config.type != TB_TYPE_PCIE_DOWN) + continue; + cap = tb_find_cap(&sw->ports[i], TB_CFG_PORT, TB_CAP_PCIE); + if (cap <= 0) + continue; + res = tb_port_read(&sw->ports[i], &data, TB_CFG_PORT, cap, 1); + if (res < 0) + continue; + if (data & 0x80000000) + continue; + return &sw->ports[i]; + } + return NULL; +} + +/** + * tb_activate_pcie_devices() - scan for and activate PCIe devices + * + * This method is somewhat ad hoc. For now it only supports one device + * per port and only devices at depth 1. + */ +static void tb_activate_pcie_devices(struct tb *tb) +{ + int i; + int cap; + u32 data; + struct tb_switch *sw; + struct tb_port *up_port; + struct tb_port *down_port; + struct tb_pci_tunnel *tunnel; + /* scan for pcie devices at depth 1*/ + for (i = 1; i <= tb->root_switch->config.max_port_number; i++) { + if (tb_is_upstream_port(&tb->root_switch->ports[i])) + continue; + if (tb->root_switch->ports[i].config.type != TB_TYPE_PORT) + continue; + if (!tb->root_switch->ports[i].remote) + continue; + sw = tb->root_switch->ports[i].remote->sw; + up_port = tb_find_pci_up_port(sw); + if (!up_port) { + tb_sw_info(sw, "no PCIe devices found, aborting\n"); + continue; + } + + /* check whether port is already activated */ + cap = tb_find_cap(up_port, TB_CFG_PORT, TB_CAP_PCIE); + if (cap <= 0) + continue; + if (tb_port_read(up_port, &data, TB_CFG_PORT, cap, 1)) + continue; + if (data & 0x80000000) { + tb_port_info(up_port, + "PCIe port already activated, aborting\n"); + continue; + } + + down_port = tb_find_unused_down_port(tb->root_switch); + if (!down_port) { + tb_port_info(up_port, + "All PCIe down ports are occupied, aborting\n"); + continue; + } + tunnel = tb_pci_alloc(tb, up_port, down_port); + if (!tunnel) { + tb_port_info(up_port, + "PCIe tunnel allocation failed, aborting\n"); + continue; + } + + if (tb_pci_activate(tunnel)) { + tb_port_info(up_port, + "PCIe tunnel activation failed, aborting\n"); + tb_pci_free(tunnel); + } + + } +} /* hotplug handling */ @@ -101,6 +220,7 @@ static void tb_handle_hotplug(struct work_struct *work) if (port->remote) { tb_port_info(port, "unplugged\n"); tb_sw_set_unpplugged(port->remote->sw); + tb_free_invalid_tunnels(tb); tb_switch_free(port->remote->sw); port->remote = NULL; } else { @@ -118,6 +238,10 @@ static void tb_handle_hotplug(struct work_struct *work) } else if (port->remote->sw->config.depth > 1) { tb_sw_warn(port->remote->sw, "hotplug: chaining not supported\n"); + } else { + tb_sw_info(port->remote->sw, + "hotplug: activating pcie devices\n"); + tb_activate_pcie_devices(tb); } } out: @@ -154,8 +278,17 @@ static void tb_schedule_hotplug_handler(void *data, u64 route, u8 port, */ void thunderbolt_shutdown_and_free(struct tb *tb) { + struct tb_pci_tunnel *tunnel; + struct tb_pci_tunnel *n; + mutex_lock(&tb->lock); + /* tunnels are only present after everything has been initialized */ + list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) { + tb_pci_deactivate(tunnel); + tb_pci_free(tunnel); + } + if (tb->root_switch) tb_switch_free(tb->root_switch); tb->root_switch = NULL; @@ -201,6 +334,7 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) tb->nhi = nhi; mutex_init(&tb->lock); mutex_lock(&tb->lock); + INIT_LIST_HEAD(&tb->tunnel_list); tb->wq = alloc_ordered_workqueue("thunderbolt", 0); if (!tb->wq) @@ -221,6 +355,7 @@ struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi) /* Full scan to discover devices added before the driver was loaded. */ tb_scan_switch(tb->root_switch); + tb_activate_pcie_devices(tb); /* Allow tb_handle_hotplug to progress events */ tb->hotplug_active = true; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 8bbdc2bc4d09..508abc426563 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -100,6 +100,7 @@ struct tb { struct tb_ctl *ctl; struct workqueue_struct *wq; /* ordered workqueue for plug events */ struct tb_switch *root_switch; + struct list_head tunnel_list; /* list of active PCIe tunnels */ bool hotplug_active; /* * tb_handle_hotplug will stop progressing plug * events and exit if this is not set (it needs to diff --git a/drivers/thunderbolt/tunnel_pci.c b/drivers/thunderbolt/tunnel_pci.c new file mode 100644 index 000000000000..baf1cd370446 --- /dev/null +++ b/drivers/thunderbolt/tunnel_pci.c @@ -0,0 +1,232 @@ +/* + * Thunderbolt Cactus Ridge driver - PCIe tunnel + * + * Copyright (c) 2014 Andreas Noever + */ + +#include +#include + +#include "tunnel_pci.h" +#include "tb.h" + +#define __TB_TUNNEL_PRINT(level, tunnel, fmt, arg...) \ + do { \ + struct tb_pci_tunnel *__tunnel = (tunnel); \ + level(__tunnel->tb, "%llx:%x <-> %llx:%x (PCI): " fmt, \ + tb_route(__tunnel->down_port->sw), \ + __tunnel->down_port->port, \ + tb_route(__tunnel->up_port->sw), \ + __tunnel->up_port->port, \ + ## arg); \ + } while (0) + +#define tb_tunnel_WARN(tunnel, fmt, arg...) \ + __TB_TUNNEL_PRINT(tb_WARN, tunnel, fmt, ##arg) +#define tb_tunnel_warn(tunnel, fmt, arg...) \ + __TB_TUNNEL_PRINT(tb_warn, tunnel, fmt, ##arg) +#define tb_tunnel_info(tunnel, fmt, arg...) \ + __TB_TUNNEL_PRINT(tb_info, tunnel, fmt, ##arg) + +static void tb_pci_init_path(struct tb_path *path) +{ + path->egress_fc_enable = TB_PATH_SOURCE | TB_PATH_INTERNAL; + path->egress_shared_buffer = TB_PATH_NONE; + path->ingress_fc_enable = TB_PATH_ALL; + path->ingress_shared_buffer = TB_PATH_NONE; + path->priority = 3; + path->weight = 1; + path->drop_packages = 0; + path->nfc_credits = 0; +} + +/** + * tb_pci_alloc() - allocate a pci tunnel + * + * Allocate a PCI tunnel. The ports must be of type TB_TYPE_PCIE_UP and + * TB_TYPE_PCIE_DOWN. + * + * Currently only paths consisting of two hops are supported (that is the + * ports must be on "adjacent" switches). + * + * The paths are hard-coded to use hop 8 (the only working hop id available on + * my thunderbolt devices). Therefore at most ONE path per device may be + * activated. + * + * Return: Returns a tb_pci_tunnel on success or NULL on failure. + */ +struct tb_pci_tunnel *tb_pci_alloc(struct tb *tb, struct tb_port *up, + struct tb_port *down) +{ + struct tb_pci_tunnel *tunnel = kzalloc(sizeof(*tunnel), GFP_KERNEL); + if (!tunnel) + goto err; + tunnel->tb = tb; + tunnel->down_port = down; + tunnel->up_port = up; + INIT_LIST_HEAD(&tunnel->list); + tunnel->path_to_up = tb_path_alloc(up->sw->tb, 2); + if (!tunnel->path_to_up) + goto err; + tunnel->path_to_down = tb_path_alloc(up->sw->tb, 2); + if (!tunnel->path_to_down) + goto err; + tb_pci_init_path(tunnel->path_to_up); + tb_pci_init_path(tunnel->path_to_down); + + tunnel->path_to_up->hops[0].in_port = down; + tunnel->path_to_up->hops[0].in_hop_index = 8; + tunnel->path_to_up->hops[0].in_counter_index = -1; + tunnel->path_to_up->hops[0].out_port = tb_upstream_port(up->sw)->remote; + tunnel->path_to_up->hops[0].next_hop_index = 8; + + tunnel->path_to_up->hops[1].in_port = tb_upstream_port(up->sw); + tunnel->path_to_up->hops[1].in_hop_index = 8; + tunnel->path_to_up->hops[1].in_counter_index = -1; + tunnel->path_to_up->hops[1].out_port = up; + tunnel->path_to_up->hops[1].next_hop_index = 8; + + tunnel->path_to_down->hops[0].in_port = up; + tunnel->path_to_down->hops[0].in_hop_index = 8; + tunnel->path_to_down->hops[0].in_counter_index = -1; + tunnel->path_to_down->hops[0].out_port = tb_upstream_port(up->sw); + tunnel->path_to_down->hops[0].next_hop_index = 8; + + tunnel->path_to_down->hops[1].in_port = + tb_upstream_port(up->sw)->remote; + tunnel->path_to_down->hops[1].in_hop_index = 8; + tunnel->path_to_down->hops[1].in_counter_index = -1; + tunnel->path_to_down->hops[1].out_port = down; + tunnel->path_to_down->hops[1].next_hop_index = 8; + return tunnel; + +err: + if (tunnel) { + if (tunnel->path_to_down) + tb_path_free(tunnel->path_to_down); + if (tunnel->path_to_up) + tb_path_free(tunnel->path_to_up); + kfree(tunnel); + } + return NULL; +} + +/** + * tb_pci_free() - free a tunnel + * + * The tunnel must have been deactivated. + */ +void tb_pci_free(struct tb_pci_tunnel *tunnel) +{ + if (tunnel->path_to_up->activated || tunnel->path_to_down->activated) { + tb_tunnel_WARN(tunnel, "trying to free an activated tunnel\n"); + return; + } + tb_path_free(tunnel->path_to_up); + tb_path_free(tunnel->path_to_down); + kfree(tunnel); +} + +/** + * tb_pci_is_invalid - check whether an activated path is still valid + */ +bool tb_pci_is_invalid(struct tb_pci_tunnel *tunnel) +{ + WARN_ON(!tunnel->path_to_up->activated); + WARN_ON(!tunnel->path_to_down->activated); + + return tb_path_is_invalid(tunnel->path_to_up) + || tb_path_is_invalid(tunnel->path_to_down); +} + +/** + * tb_pci_port_active() - activate/deactivate PCI capability + * + * Return: Returns 0 on success or an error code on failure. + */ +static int tb_pci_port_active(struct tb_port *port, bool active) +{ + u32 word = active ? 0x80000000 : 0x0; + int cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PCIE); + if (cap <= 0) { + tb_port_warn(port, "TB_CAP_PCIE not found: %d\n", cap); + return cap ? cap : -ENXIO; + } + return tb_port_write(port, &word, TB_CFG_PORT, cap, 1); +} + +/** + * tb_pci_restart() - activate a tunnel after a hardware reset + */ +int tb_pci_restart(struct tb_pci_tunnel *tunnel) +{ + int res; + tunnel->path_to_up->activated = false; + tunnel->path_to_down->activated = false; + + tb_tunnel_info(tunnel, "activating\n"); + + res = tb_path_activate(tunnel->path_to_up); + if (res) + goto err; + res = tb_path_activate(tunnel->path_to_down); + if (res) + goto err; + + res = tb_pci_port_active(tunnel->down_port, true); + if (res) + goto err; + + res = tb_pci_port_active(tunnel->up_port, true); + if (res) + goto err; + return 0; +err: + tb_tunnel_warn(tunnel, "activation failed\n"); + tb_pci_deactivate(tunnel); + return res; +} + +/** + * tb_pci_activate() - activate a tunnel + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_pci_activate(struct tb_pci_tunnel *tunnel) +{ + int res; + if (tunnel->path_to_up->activated || tunnel->path_to_down->activated) { + tb_tunnel_WARN(tunnel, + "trying to activate an already activated tunnel\n"); + return -EINVAL; + } + + res = tb_pci_restart(tunnel); + if (res) + return res; + + list_add(&tunnel->list, &tunnel->tb->tunnel_list); + return 0; +} + + + +/** + * tb_pci_deactivate() - deactivate a tunnel + */ +void tb_pci_deactivate(struct tb_pci_tunnel *tunnel) +{ + tb_tunnel_info(tunnel, "deactivating\n"); + /* + * TODO: enable reset by writing 0x04000000 to TB_CAP_PCIE + 1 on up + * port. Seems to have no effect? + */ + tb_pci_port_active(tunnel->up_port, false); + tb_pci_port_active(tunnel->down_port, false); + if (tunnel->path_to_down->activated) + tb_path_deactivate(tunnel->path_to_down); + if (tunnel->path_to_up->activated) + tb_path_deactivate(tunnel->path_to_up); + list_del_init(&tunnel->list); +} + diff --git a/drivers/thunderbolt/tunnel_pci.h b/drivers/thunderbolt/tunnel_pci.h new file mode 100644 index 000000000000..a67f93c140fa --- /dev/null +++ b/drivers/thunderbolt/tunnel_pci.h @@ -0,0 +1,30 @@ +/* + * Thunderbolt Cactus Ridge driver - PCIe tunnel + * + * Copyright (c) 2014 Andreas Noever + */ + +#ifndef TB_PCI_H_ +#define TB_PCI_H_ + +#include "tb.h" + +struct tb_pci_tunnel { + struct tb *tb; + struct tb_port *up_port; + struct tb_port *down_port; + struct tb_path *path_to_up; + struct tb_path *path_to_down; + struct list_head list; +}; + +struct tb_pci_tunnel *tb_pci_alloc(struct tb *tb, struct tb_port *up, + struct tb_port *down); +void tb_pci_free(struct tb_pci_tunnel *tunnel); +int tb_pci_activate(struct tb_pci_tunnel *tunnel); +int tb_pci_restart(struct tb_pci_tunnel *tunnel); +void tb_pci_deactivate(struct tb_pci_tunnel *tunnel); +bool tb_pci_is_invalid(struct tb_pci_tunnel *tunnel); + +#endif + -- cgit v1.2.3 From 7d2a01b87f1682fde87461864e6682031bfaa0a9 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:09 +0200 Subject: PCI: Add pci_fixup_suspend_late quirk pass Add pci_fixup_suspend_late as a new pci_fixup_pass. The pass is called from suspend_noirq and poweroff_noirq. Using the same pass for suspend and hibernate is consistent with resume_early which is called by resume_noirq and restore_noirq. The new quirk pass is required for Thunderbolt support on Apple hardware. Signed-off-by: Andreas Noever Acked-by: Bjorn Helgaas Signed-off-by: Greg Kroah-Hartman --- drivers/pci/pci-driver.c | 18 ++++++++++++++---- drivers/pci/quirks.c | 7 +++++++ include/asm-generic/vmlinux.lds.h | 3 +++ include/linux/pci.h | 12 +++++++++++- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c index 3f8e3dbcaa7c..d04c5adafc16 100644 --- a/drivers/pci/pci-driver.c +++ b/drivers/pci/pci-driver.c @@ -582,7 +582,7 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state) WARN_ONCE(pci_dev->current_state != prev, "PCI PM: Device state not saved by %pF\n", drv->suspend_late); - return 0; + goto Fixup; } } @@ -591,6 +591,9 @@ static int pci_legacy_suspend_late(struct device *dev, pm_message_t state) pci_pm_set_unknown_state(pci_dev); +Fixup: + pci_fixup_device(pci_fixup_suspend_late, pci_dev); + return 0; } @@ -734,7 +737,7 @@ static int pci_pm_suspend_noirq(struct device *dev) if (!pm) { pci_save_state(pci_dev); - return 0; + goto Fixup; } if (pm->suspend_noirq) { @@ -751,7 +754,7 @@ static int pci_pm_suspend_noirq(struct device *dev) WARN_ONCE(pci_dev->current_state != prev, "PCI PM: State of device not saved by %pF\n", pm->suspend_noirq); - return 0; + goto Fixup; } } @@ -775,6 +778,9 @@ static int pci_pm_suspend_noirq(struct device *dev) if (pci_dev->class == PCI_CLASS_SERIAL_USB_EHCI) pci_write_config_word(pci_dev, PCI_COMMAND, 0); +Fixup: + pci_fixup_device(pci_fixup_suspend_late, pci_dev); + return 0; } @@ -999,8 +1005,10 @@ static int pci_pm_poweroff_noirq(struct device *dev) if (pci_has_legacy_pm_support(to_pci_dev(dev))) return pci_legacy_suspend_late(dev, PMSG_HIBERNATE); - if (!drv || !drv->pm) + if (!drv || !drv->pm) { + pci_fixup_device(pci_fixup_suspend_late, pci_dev); return 0; + } if (drv->pm->poweroff_noirq) { int error; @@ -1021,6 +1029,8 @@ static int pci_pm_poweroff_noirq(struct device *dev) if (pci_dev->class == PCI_CLASS_SERIAL_USB_EHCI) pci_write_config_word(pci_dev, PCI_COMMAND, 0); + pci_fixup_device(pci_fixup_suspend_late, pci_dev); + if (pcibios_pm_ops.poweroff_noirq) return pcibios_pm_ops.poweroff_noirq(dev); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index d0f69269eb6c..03266af20d5f 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -3018,6 +3018,8 @@ extern struct pci_fixup __start_pci_fixups_resume_early[]; extern struct pci_fixup __end_pci_fixups_resume_early[]; extern struct pci_fixup __start_pci_fixups_suspend[]; extern struct pci_fixup __end_pci_fixups_suspend[]; +extern struct pci_fixup __start_pci_fixups_suspend_late[]; +extern struct pci_fixup __end_pci_fixups_suspend_late[]; static bool pci_apply_fixup_final_quirks; @@ -3063,6 +3065,11 @@ void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev) end = __end_pci_fixups_suspend; break; + case pci_fixup_suspend_late: + start = __start_pci_fixups_suspend_late; + end = __end_pci_fixups_suspend_late; + break; + default: /* stupid compiler warning, you would think with an enum... */ return; diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 471ba48c7ae4..47cd98656f9d 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -268,6 +268,9 @@ VMLINUX_SYMBOL(__start_pci_fixups_suspend) = .; \ *(.pci_fixup_suspend) \ VMLINUX_SYMBOL(__end_pci_fixups_suspend) = .; \ + VMLINUX_SYMBOL(__start_pci_fixups_suspend_late) = .; \ + *(.pci_fixup_suspend_late) \ + VMLINUX_SYMBOL(__end_pci_fixups_suspend_late) = .; \ } \ \ /* Built-in firmware blobs */ \ diff --git a/include/linux/pci.h b/include/linux/pci.h index 466bcd111d85..295d3a9d8ffe 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -1477,8 +1477,9 @@ enum pci_fixup_pass { pci_fixup_final, /* Final phase of device fixups */ pci_fixup_enable, /* pci_enable_device() time */ pci_fixup_resume, /* pci_device_resume() */ - pci_fixup_suspend, /* pci_device_suspend */ + pci_fixup_suspend, /* pci_device_suspend() */ pci_fixup_resume_early, /* pci_device_resume_early() */ + pci_fixup_suspend_late, /* pci_device_suspend_late() */ }; /* Anonymous variables would be nice... */ @@ -1519,6 +1520,11 @@ enum pci_fixup_pass { DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend, \ suspend##hook, vendor, device, class, \ class_shift, hook) +#define DECLARE_PCI_FIXUP_CLASS_SUSPEND_LATE(vendor, device, class, \ + class_shift, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend_late, \ + suspend_late##hook, vendor, device, \ + class, class_shift, hook) #define DECLARE_PCI_FIXUP_EARLY(vendor, device, hook) \ DECLARE_PCI_FIXUP_SECTION(.pci_fixup_early, \ @@ -1544,6 +1550,10 @@ enum pci_fixup_pass { DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend, \ suspend##hook, vendor, device, \ PCI_ANY_ID, 0, hook) +#define DECLARE_PCI_FIXUP_SUSPEND_LATE(vendor, device, hook) \ + DECLARE_PCI_FIXUP_SECTION(.pci_fixup_suspend_late, \ + suspend_late##hook, vendor, device, \ + PCI_ANY_ID, 0, hook) #ifdef CONFIG_PCI_QUIRKS void pci_fixup_device(enum pci_fixup_pass pass, struct pci_dev *dev); -- cgit v1.2.3 From 1df5172c5c251ec24a1bd0f44fe38c841f384330 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:10 +0200 Subject: PCI: Suspend/resume quirks for Apple thunderbolt Add two quirks to support thunderbolt suspend/resume on Apple systems. We need to perform two different actions during suspend and resume: The whole controller has to be powered down before suspend. If this is not done then the native host interface device will be gone after resume if a thunderbolt device was plugged in before suspending. The controller represents itself as multiple PCI devices/bridges. To power it down we hook into the upstream bridge of the controller and call the magic ACPI methods. Power will be restored automatically during resume (by the firmware presumably). During resume we have to wait for the native host interface to reestablish all pci tunnels. Since there is no parent-child relationship between the NHI and the bridges we have to explicitly wait for them using device_pm_wait_for_dev. We do this in the resume_noirq phase of the downstream bridges of the controller (which lead into the thunderbolt tunnels). Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/pci/quirks.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 03266af20d5f..ca8a171f9689 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2986,6 +2986,103 @@ DECLARE_PCI_FIXUP_HEADER(0x1814, 0x0601, /* Ralink RT2800 802.11n PCI */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_REALTEK, 0x8169, quirk_broken_intx_masking); +#ifdef CONFIG_ACPI +/* + * Apple: Shutdown Cactus Ridge Thunderbolt controller. + * + * On Apple hardware the Cactus Ridge Thunderbolt controller needs to be + * shutdown before suspend. Otherwise the native host interface (NHI) will not + * be present after resume if a device was plugged in before suspend. + * + * The thunderbolt controller consists of a pcie switch with downstream + * bridges leading to the NHI and to the tunnel pci bridges. + * + * This quirk cuts power to the whole chip. Therefore we have to apply it + * during suspend_noirq of the upstream bridge. + * + * Power is automagically restored before resume. No action is needed. + */ +static void quirk_apple_poweroff_thunderbolt(struct pci_dev *dev) +{ + acpi_handle bridge, SXIO, SXFP, SXLV; + + if (!dmi_match(DMI_BOARD_VENDOR, "Apple Inc.")) + return; + if (pci_pcie_type(dev) != PCI_EXP_TYPE_UPSTREAM) + return; + bridge = ACPI_HANDLE(&dev->dev); + if (!bridge) + return; + /* + * SXIO and SXLV are present only on machines requiring this quirk. + * TB bridges in external devices might have the same device id as those + * on the host, but they will not have the associated ACPI methods. This + * implicitly checks that we are at the right bridge. + */ + if (ACPI_FAILURE(acpi_get_handle(bridge, "DSB0.NHI0.SXIO", &SXIO)) + || ACPI_FAILURE(acpi_get_handle(bridge, "DSB0.NHI0.SXFP", &SXFP)) + || ACPI_FAILURE(acpi_get_handle(bridge, "DSB0.NHI0.SXLV", &SXLV))) + return; + dev_info(&dev->dev, "quirk: cutting power to thunderbolt controller...\n"); + + /* magic sequence */ + acpi_execute_simple_method(SXIO, NULL, 1); + acpi_execute_simple_method(SXFP, NULL, 0); + msleep(300); + acpi_execute_simple_method(SXLV, NULL, 0); + acpi_execute_simple_method(SXIO, NULL, 0); + acpi_execute_simple_method(SXLV, NULL, 0); +} +DECLARE_PCI_FIXUP_SUSPEND_LATE(PCI_VENDOR_ID_INTEL, 0x1547, + quirk_apple_poweroff_thunderbolt); + +/* + * Apple: Wait for the thunderbolt controller to reestablish pci tunnels. + * + * During suspend the thunderbolt controller is reset and all pci + * tunnels are lost. The NHI driver will try to reestablish all tunnels + * during resume. We have to manually wait for the NHI since there is + * no parent child relationship between the NHI and the tunneled + * bridges. + */ +static void quirk_apple_wait_for_thunderbolt(struct pci_dev *dev) +{ + struct pci_dev *sibling = NULL; + struct pci_dev *nhi = NULL; + + if (!dmi_match(DMI_BOARD_VENDOR, "Apple Inc.")) + return; + if (pci_pcie_type(dev) != PCI_EXP_TYPE_DOWNSTREAM) + return; + /* + * Find the NHI and confirm that we are a bridge on the tb host + * controller and not on a tb endpoint. + */ + sibling = pci_get_slot(dev->bus, 0x0); + if (sibling == dev) + goto out; /* we are the downstream bridge to the NHI */ + if (!sibling || !sibling->subordinate) + goto out; + nhi = pci_get_slot(sibling->subordinate, 0x0); + if (!nhi) + goto out; + if (nhi->vendor != PCI_VENDOR_ID_INTEL + || (nhi->device != 0x1547 && nhi->device != 0x156c) + || nhi->subsystem_vendor != 0x2222 + || nhi->subsystem_device != 0x1111) + goto out; + dev_info(&dev->dev, "quirk: wating for thunderbolt to reestablish pci tunnels...\n"); + device_pm_wait_for_dev(&dev->dev, &nhi->dev); +out: + pci_dev_put(nhi); + pci_dev_put(sibling); +} +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, 0x1547, + quirk_apple_wait_for_thunderbolt); +DECLARE_PCI_FIXUP_RESUME_EARLY(PCI_VENDOR_ID_INTEL, 0x156d, + quirk_apple_wait_for_thunderbolt); +#endif + static void pci_do_fixups(struct pci_dev *dev, struct pci_fixup *f, struct pci_fixup *end) { -- cgit v1.2.3 From c90553b3c4ac2389a71a5c012b6e5bb1160d48a7 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:11 +0200 Subject: thunderbolt: Read switch uid from EEPROM Add eeprom access code and read the uid during switch initialization. The UID will be used to check device identity after suspend. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Makefile | 2 +- drivers/thunderbolt/eeprom.c | 189 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 5 ++ drivers/thunderbolt/tb.h | 3 + 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 drivers/thunderbolt/eeprom.c diff --git a/drivers/thunderbolt/Makefile b/drivers/thunderbolt/Makefile index 0122ca698f78..5d1053cdfa54 100644 --- a/drivers/thunderbolt/Makefile +++ b/drivers/thunderbolt/Makefile @@ -1,3 +1,3 @@ obj-${CONFIG_THUNDERBOLT} := thunderbolt.o -thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o +thunderbolt-objs := nhi.o ctl.o tb.o switch.o cap.o path.o tunnel_pci.o eeprom.o diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c new file mode 100644 index 000000000000..f28e40231c9e --- /dev/null +++ b/drivers/thunderbolt/eeprom.c @@ -0,0 +1,189 @@ +/* + * Thunderbolt Cactus Ridge driver - eeprom access + * + * Copyright (c) 2014 Andreas Noever + */ + +#include "tb.h" + +/** + * tb_eeprom_ctl_write() - write control word + */ +static int tb_eeprom_ctl_write(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) +{ + return tb_sw_write(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1); +} + +/** + * tb_eeprom_ctl_write() - read control word + */ +static int tb_eeprom_ctl_read(struct tb_switch *sw, struct tb_eeprom_ctl *ctl) +{ + return tb_sw_read(sw, ctl, TB_CFG_SWITCH, sw->cap_plug_events + 4, 1); +} + +enum tb_eeprom_transfer { + TB_EEPROM_IN, + TB_EEPROM_OUT, +}; + +/** + * tb_eeprom_active - enable rom access + * + * WARNING: Always disable access after usage. Otherwise the controller will + * fail to reprobe. + */ +static int tb_eeprom_active(struct tb_switch *sw, bool enable) +{ + struct tb_eeprom_ctl ctl; + int res = tb_eeprom_ctl_read(sw, &ctl); + if (res) + return res; + if (enable) { + ctl.access_high = 1; + res = tb_eeprom_ctl_write(sw, &ctl); + if (res) + return res; + ctl.access_low = 0; + return tb_eeprom_ctl_write(sw, &ctl); + } else { + ctl.access_low = 1; + res = tb_eeprom_ctl_write(sw, &ctl); + if (res) + return res; + ctl.access_high = 0; + return tb_eeprom_ctl_write(sw, &ctl); + } +} + +/** + * tb_eeprom_transfer - transfer one bit + * + * If TB_EEPROM_IN is passed, then the bit can be retrieved from ctl->data_in. + * If TB_EEPROM_OUT is passed, then ctl->data_out will be written. + */ +static int tb_eeprom_transfer(struct tb_switch *sw, struct tb_eeprom_ctl *ctl, + enum tb_eeprom_transfer direction) +{ + int res; + if (direction == TB_EEPROM_OUT) { + res = tb_eeprom_ctl_write(sw, ctl); + if (res) + return res; + } + ctl->clock = 1; + res = tb_eeprom_ctl_write(sw, ctl); + if (res) + return res; + if (direction == TB_EEPROM_IN) { + res = tb_eeprom_ctl_read(sw, ctl); + if (res) + return res; + } + ctl->clock = 0; + return tb_eeprom_ctl_write(sw, ctl); +} + +/** + * tb_eeprom_out - write one byte to the bus + */ +static int tb_eeprom_out(struct tb_switch *sw, u8 val) +{ + struct tb_eeprom_ctl ctl; + int i; + int res = tb_eeprom_ctl_read(sw, &ctl); + if (res) + return res; + for (i = 0; i < 8; i++) { + ctl.data_out = val & 0x80; + res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_OUT); + if (res) + return res; + val <<= 1; + } + return 0; +} + +/** + * tb_eeprom_in - read one byte from the bus + */ +static int tb_eeprom_in(struct tb_switch *sw, u8 *val) +{ + struct tb_eeprom_ctl ctl; + int i; + int res = tb_eeprom_ctl_read(sw, &ctl); + if (res) + return res; + *val = 0; + for (i = 0; i < 8; i++) { + *val <<= 1; + res = tb_eeprom_transfer(sw, &ctl, TB_EEPROM_IN); + if (res) + return res; + *val |= ctl.data_in; + } + return 0; +} + +/** + * tb_eeprom_read_n - read count bytes from offset into val + */ +static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val, + size_t count) +{ + int i, res; + res = tb_eeprom_active(sw, true); + if (res) + return res; + res = tb_eeprom_out(sw, 3); + if (res) + return res; + res = tb_eeprom_out(sw, offset >> 8); + if (res) + return res; + res = tb_eeprom_out(sw, offset); + if (res) + return res; + for (i = 0; i < count; i++) { + res = tb_eeprom_in(sw, val + i); + if (res) + return res; + } + return tb_eeprom_active(sw, false); +} + +int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) +{ + u8 data[9]; + struct tb_cap_plug_events cap; + int res; + if (!sw->cap_plug_events) { + tb_sw_warn(sw, "no TB_CAP_PLUG_EVENTS, cannot read eeprom\n"); + return -ENOSYS; + } + res = tb_sw_read(sw, &cap, TB_CFG_SWITCH, sw->cap_plug_events, + sizeof(cap) / 4); + if (res) + return res; + if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) { + tb_sw_warn(sw, "no NVM\n"); + return -ENOSYS; + } + + if (cap.drom_offset > 0xffff) { + tb_sw_warn(sw, "drom offset is larger than 0xffff: %#x\n", + cap.drom_offset); + return -ENXIO; + } + + /* read uid */ + res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9); + if (res) + return res; + /* TODO: check checksum in data[0] */ + *uid = *(u64 *)(data+1); + return 0; +} + + + diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 667413f3ad7a..aeb5c30f8d76 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -376,6 +376,11 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; + if (tb_eeprom_read_uid(sw, &sw->uid)) + tb_sw_warn(sw, "could not read uid from eeprom\n"); + else + tb_sw_info(sw, "uid: %#llx\n", sw->uid); + if (tb_plug_events_active(sw, true)) goto err; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 508abc426563..a89087f2da71 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -19,6 +19,7 @@ struct tb_switch { struct tb_regs_switch_header config; struct tb_port *ports; struct tb *tb; + u64 uid; int cap_plug_events; /* offset, zero if not found */ bool is_unplugged; /* unplugged, will go away */ }; @@ -231,6 +232,8 @@ int tb_path_activate(struct tb_path *path); void tb_path_deactivate(struct tb_path *path); bool tb_path_is_invalid(struct tb_path *path); +int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid); + static inline int tb_route_length(u64 route) { -- cgit v1.2.3 From 23dd5bb49d986f37977ed80dd2ca65040ead4392 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Tue, 3 Jun 2014 22:04:12 +0200 Subject: thunderbolt: Add suspend/hibernate support We use _noirq since we have to restore the pci tunnels before the pci core wakes the tunneled devices. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 33 +++++++++++++++++ drivers/thunderbolt/switch.c | 84 ++++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.c | 61 ++++++++++++++++++++++++++++++++ drivers/thunderbolt/tb.h | 5 +++ 4 files changed, 183 insertions(+) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index d2b9ce857818..346b41e7d5d1 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -7,6 +7,7 @@ * Copyright (c) 2014 Andreas Noever */ +#include #include #include #include @@ -492,6 +493,22 @@ static irqreturn_t nhi_msi(int irq, void *data) return IRQ_HANDLED; } +static int nhi_suspend_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + thunderbolt_suspend(tb); + return 0; +} + +static int nhi_resume_noirq(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct tb *tb = pci_get_drvdata(pdev); + thunderbolt_resume(tb); + return 0; +} + static void nhi_shutdown(struct tb_nhi *nhi) { int i; @@ -600,6 +617,21 @@ static void nhi_remove(struct pci_dev *pdev) nhi_shutdown(nhi); } +/* + * The tunneled pci bridges are siblings of us. Use resume_noirq to reenable + * the tunnels asap. A corresponding pci quirk blocks the downstream bridges + * resume_noirq until we are done. + */ +static const struct dev_pm_ops nhi_pm_ops = { + .suspend_noirq = nhi_suspend_noirq, + .resume_noirq = nhi_resume_noirq, + .freeze_noirq = nhi_suspend_noirq, /* + * we just disable hotplug, the + * pci-tunnels stay alive. + */ + .restore_noirq = nhi_resume_noirq, +}; + struct pci_device_id nhi_ids[] = { /* * We have to specify class, the TB bridges use the same device and @@ -626,6 +658,7 @@ static struct pci_driver nhi_driver = { .id_table = nhi_ids, .probe = nhi_probe, .remove = nhi_remove, + .driver.pm = &nhi_pm_ops, }; static int __init nhi_init(void) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index aeb5c30f8d76..c2a24b6fb883 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -229,6 +229,30 @@ static void tb_dump_switch(struct tb *tb, struct tb_regs_switch_header *sw) sw->__unknown1, sw->__unknown4); } +/** + * reset_switch() - reconfigure route, enable and send TB_CFG_PKG_RESET + * + * Return: Returns 0 on success or an error code on failure. + */ +int tb_switch_reset(struct tb *tb, u64 route) +{ + struct tb_cfg_result res; + struct tb_regs_switch_header header = { + header.route_hi = route >> 32, + header.route_lo = route, + header.enabled = true, + }; + tb_info(tb, "resetting switch at %llx\n", route); + res.err = tb_cfg_write(tb->ctl, ((u32 *) &header) + 2, route, + 0, 2, 2, 2); + if (res.err) + return res.err; + res = tb_cfg_reset(tb->ctl, route, TB_CFG_DEFAULT_TIMEOUT); + if (res.err > 0) + return -EIO; + return res.err; +} + struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) { u8 next_port = route; /* @@ -412,3 +436,63 @@ void tb_sw_set_unpplugged(struct tb_switch *sw) } } +int tb_switch_resume(struct tb_switch *sw) +{ + int i, err; + u64 uid; + tb_sw_info(sw, "resuming switch\n"); + + err = tb_eeprom_read_uid(sw, &uid); + if (err) { + tb_sw_warn(sw, "uid read failed\n"); + return err; + } + if (sw->uid != uid) { + tb_sw_info(sw, + "changed while suspended (uid %#llx -> %#llx)\n", + sw->uid, uid); + return -ENODEV; + } + + /* upload configuration */ + err = tb_sw_write(sw, 1 + (u32 *) &sw->config, TB_CFG_SWITCH, 1, 3); + if (err) + return err; + + err = tb_plug_events_active(sw, true); + if (err) + return err; + + /* check for surviving downstream switches */ + for (i = 1; i <= sw->config.max_port_number; i++) { + struct tb_port *port = &sw->ports[i]; + if (tb_is_upstream_port(port)) + continue; + if (!port->remote) + continue; + if (tb_wait_for_port(port, true) <= 0 + || tb_switch_resume(port->remote->sw)) { + tb_port_warn(port, + "lost during suspend, disconnecting\n"); + tb_sw_set_unpplugged(port->remote->sw); + } + } + return 0; +} + +void tb_switch_suspend(struct tb_switch *sw) +{ + int i, err; + err = tb_plug_events_active(sw, false); + if (err) + return; + + for (i = 1; i <= sw->config.max_port_number; i++) { + if (!tb_is_upstream_port(&sw->ports[i]) && sw->ports[i].remote) + tb_switch_suspend(sw->ports[i].remote->sw); + } + /* + * TODO: invoke tb_cfg_prepare_to_sleep here? does not seem to have any + * effect? + */ +} diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 177f61df464d..1aa6dd7dc68b 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -68,6 +68,28 @@ static void tb_free_invalid_tunnels(struct tb *tb) } } +/** + * tb_free_unplugged_children() - traverse hierarchy and free unplugged switches + */ +static void tb_free_unplugged_children(struct tb_switch *sw) +{ + int i; + for (i = 1; i <= sw->config.max_port_number; i++) { + struct tb_port *port = &sw->ports[i]; + if (tb_is_upstream_port(port)) + continue; + if (!port->remote) + continue; + if (port->remote->sw->is_unplugged) { + tb_switch_free(port->remote->sw); + port->remote = NULL; + } else { + tb_free_unplugged_children(port->remote->sw); + } + } +} + + /** * find_pci_up_port() - return the first PCIe up port on @sw or NULL */ @@ -368,3 +390,42 @@ err_locked: return NULL; } +void thunderbolt_suspend(struct tb *tb) +{ + tb_info(tb, "suspending...\n"); + mutex_lock(&tb->lock); + tb_switch_suspend(tb->root_switch); + tb_ctl_stop(tb->ctl); + tb->hotplug_active = false; /* signal tb_handle_hotplug to quit */ + mutex_unlock(&tb->lock); + tb_info(tb, "suspend finished\n"); +} + +void thunderbolt_resume(struct tb *tb) +{ + struct tb_pci_tunnel *tunnel, *n; + tb_info(tb, "resuming...\n"); + mutex_lock(&tb->lock); + tb_ctl_start(tb->ctl); + + /* remove any pci devices the firmware might have setup */ + tb_switch_reset(tb, 0); + + tb_switch_resume(tb->root_switch); + tb_free_invalid_tunnels(tb); + tb_free_unplugged_children(tb->root_switch); + list_for_each_entry_safe(tunnel, n, &tb->tunnel_list, list) + tb_pci_restart(tunnel); + if (!list_empty(&tb->tunnel_list)) { + /* + * the pcie links need some time to get going. + * 100ms works for me... + */ + tb_info(tb, "tunnels restarted, sleeping for 100ms\n"); + msleep(100); + } + /* Allow tb_handle_hotplug to progress events */ + tb->hotplug_active = true; + mutex_unlock(&tb->lock); + tb_info(tb, "resume finished\n"); +} diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index a89087f2da71..63e89d01047c 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -214,9 +214,14 @@ static inline int tb_port_write(struct tb_port *port, void *buffer, struct tb *thunderbolt_alloc_and_start(struct tb_nhi *nhi); void thunderbolt_shutdown_and_free(struct tb *tb); +void thunderbolt_suspend(struct tb *tb); +void thunderbolt_resume(struct tb *tb); struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route); void tb_switch_free(struct tb_switch *sw); +void tb_switch_suspend(struct tb_switch *sw); +int tb_switch_resume(struct tb_switch *sw); +int tb_switch_reset(struct tb *tb, u64 route); void tb_sw_set_unpplugged(struct tb_switch *sw); struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route); -- cgit v1.2.3 From cd22e73bdf5eff7e68a0f8bdfbce123ad43651f6 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Thu, 12 Jun 2014 23:11:46 +0200 Subject: thunderbolt: Read port configuration from eeprom. All Thunderbolt switches (except the root switch) contain a drom which contains information about the device. Right now we only read the UID. Add code to read and parse this drom. For now we are only interested in which ports are disabled and which ports are "dual link ports" (a physical thunderbolt port/socket contains two such ports). Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/eeprom.c | 266 ++++++++++++++++++++++++++++++++++++++++++- drivers/thunderbolt/switch.c | 4 +- drivers/thunderbolt/tb.h | 7 +- 3 files changed, 270 insertions(+), 7 deletions(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index f28e40231c9e..0d5a80b2d07a 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -4,6 +4,7 @@ * Copyright (c) 2014 Andreas Noever */ +#include #include "tb.h" /** @@ -152,9 +153,86 @@ static int tb_eeprom_read_n(struct tb_switch *sw, u16 offset, u8 *val, return tb_eeprom_active(sw, false); } -int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) +static u8 tb_crc8(u8 *data, int len) +{ + int i, j; + u8 val = 0xff; + for (i = 0; i < len; i++) { + val ^= data[i]; + for (j = 0; j < 8; j++) + val = (val << 1) ^ ((val & 0x80) ? 7 : 0); + } + return val; +} + +static u32 tb_crc32(void *data, size_t len) +{ + return ~__crc32c_le(~0, data, len); +} + +#define TB_DROM_DATA_START 13 +struct tb_drom_header { + /* BYTE 0 */ + u8 uid_crc8; /* checksum for uid */ + /* BYTES 1-8 */ + u64 uid; + /* BYTES 9-12 */ + u32 data_crc32; /* checksum for data_len bytes starting at byte 13 */ + /* BYTE 13 */ + u8 device_rom_revision; /* should be <= 1 */ + u16 data_len:10; + u8 __unknown1:6; + /* BYTES 16-21 */ + u16 vendor_id; + u16 model_id; + u8 model_rev; + u8 eeprom_rev; +} __packed; + +enum tb_drom_entry_type { + TB_DROM_ENTRY_GENERIC, + TB_DROM_ENTRY_PORT, +}; + +struct tb_drom_entry_header { + u8 len; + u8 index:6; + bool port_disabled:1; /* only valid if type is TB_DROM_ENTRY_PORT */ + enum tb_drom_entry_type type:1; +} __packed; + +struct tb_drom_entry_port { + /* BYTES 0-1 */ + struct tb_drom_entry_header header; + /* BYTE 2 */ + u8 dual_link_port_rid:4; + u8 link_nr:1; + u8 unknown1:2; + bool has_dual_link_port:1; + + /* BYTE 3 */ + u8 dual_link_port_nr:6; + u8 unknown2:2; + + /* BYTES 4 - 5 TODO decode */ + u8 micro2:4; + u8 micro1:4; + u8 micro3; + + /* BYTES 5-6, TODO: verify (find hardware that has these set) */ + u8 peer_port_rid:4; + u8 unknown3:3; + bool has_peer_port:1; + u8 peer_port_nr:6; + u8 unknown4:2; +} __packed; + + +/** + * tb_eeprom_get_drom_offset - get drom offset within eeprom + */ +int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) { - u8 data[9]; struct tb_cap_plug_events cap; int res; if (!sw->cap_plug_events) { @@ -165,6 +243,7 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) sizeof(cap) / 4); if (res) return res; + if (!cap.eeprom_ctl.present || cap.eeprom_ctl.not_present) { tb_sw_warn(sw, "no NVM\n"); return -ENOSYS; @@ -175,15 +254,194 @@ int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid) cap.drom_offset); return -ENXIO; } + *offset = cap.drom_offset; + return 0; +} + +/** + * tb_drom_read_uid_only - read uid directly from drom + * + * Does not use the cached copy in sw->drom. Used during resume to check switch + * identity. + */ +int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid) +{ + u8 data[9]; + u16 drom_offset; + u8 crc; + int res = tb_eeprom_get_drom_offset(sw, &drom_offset); + if (res) + return res; /* read uid */ - res = tb_eeprom_read_n(sw, cap.drom_offset, data, 9); + res = tb_eeprom_read_n(sw, drom_offset, data, 9); if (res) return res; - /* TODO: check checksum in data[0] */ + + crc = tb_crc8(data + 1, 8); + if (crc != data[0]) { + tb_sw_warn(sw, "uid crc8 missmatch (expected: %#x, got: %#x)\n", + data[0], crc); + return -EIO; + } + *uid = *(u64 *)(data+1); return 0; } +static void tb_drom_parse_port_entry(struct tb_port *port, + struct tb_drom_entry_port *entry) +{ + port->link_nr = entry->link_nr; + if (entry->has_dual_link_port) + port->dual_link_port = + &port->sw->ports[entry->dual_link_port_nr]; +} + +static int tb_drom_parse_entry(struct tb_switch *sw, + struct tb_drom_entry_header *header) +{ + struct tb_port *port; + int res; + enum tb_port_type type; + if (header->type != TB_DROM_ENTRY_PORT) + return 0; + port = &sw->ports[header->index]; + port->disabled = header->port_disabled; + if (port->disabled) + return 0; + + res = tb_port_read(port, &type, TB_CFG_PORT, 2, 1); + if (res) + return res; + type &= 0xffffff; + + if (type == TB_TYPE_PORT) { + struct tb_drom_entry_port *entry = (void *) header; + if (header->len != sizeof(*entry)) { + tb_sw_warn(sw, + "port entry has size %#x (expected %#lx)\n", + header->len, sizeof(struct tb_drom_entry_port)); + return -EIO; + } + tb_drom_parse_port_entry(port, entry); + } + return 0; +} + +/** + * tb_drom_parse_entries - parse the linked list of drom entries + * + * Drom must have been copied to sw->drom. + */ +static int tb_drom_parse_entries(struct tb_switch *sw) +{ + struct tb_drom_header *header = (void *) sw->drom; + u16 pos = sizeof(*header); + u16 drom_size = header->data_len + TB_DROM_DATA_START; + + while (pos < drom_size) { + struct tb_drom_entry_header *entry = (void *) (sw->drom + pos); + if (pos + 1 == drom_size || pos + entry->len > drom_size + || !entry->len) { + tb_sw_warn(sw, "drom buffer overrun, aborting\n"); + return -EIO; + } + + tb_drom_parse_entry(sw, entry); + + pos += entry->len; + } + return 0; +} + +/** + * tb_drom_read - copy drom to sw->drom and parse it + */ +int tb_drom_read(struct tb_switch *sw) +{ + u16 drom_offset; + u16 size; + u32 crc; + struct tb_drom_header *header; + int res; + if (sw->drom) + return 0; + + if (tb_route(sw) == 0) { + /* + * The root switch contains only a dummy drom (header only, + * no entries). Hardcode the configuration here. + */ + tb_drom_read_uid_only(sw, &sw->uid); + + sw->ports[1].link_nr = 0; + sw->ports[2].link_nr = 1; + sw->ports[1].dual_link_port = &sw->ports[2]; + sw->ports[2].dual_link_port = &sw->ports[1]; + + sw->ports[3].link_nr = 0; + sw->ports[4].link_nr = 1; + sw->ports[3].dual_link_port = &sw->ports[4]; + sw->ports[4].dual_link_port = &sw->ports[3]; + return 0; + } + + res = tb_eeprom_get_drom_offset(sw, &drom_offset); + if (res) + return res; + + res = tb_eeprom_read_n(sw, drom_offset + 14, (u8 *) &size, 2); + if (res) + return res; + size &= 0x3ff; + size += TB_DROM_DATA_START; + tb_sw_info(sw, "reading drom (length: %#x)\n", size); + if (size < sizeof(*header)) { + tb_sw_warn(sw, "drom too small, aborting\n"); + return -EIO; + } + + sw->drom = kzalloc(size, GFP_KERNEL); + if (!sw->drom) + return -ENOMEM; + res = tb_eeprom_read_n(sw, drom_offset, sw->drom, size); + if (res) + goto err; + + header = (void *) sw->drom; + + if (header->data_len + TB_DROM_DATA_START != size) { + tb_sw_warn(sw, "drom size mismatch, aborting\n"); + goto err; + } + + crc = tb_crc8((u8 *) &header->uid, 8); + if (crc != header->uid_crc8) { + tb_sw_warn(sw, + "drom uid crc8 mismatch (expected: %#x, got: %#x), aborting\n", + header->uid_crc8, crc); + goto err; + } + sw->uid = header->uid; + + crc = tb_crc32(sw->drom + TB_DROM_DATA_START, header->data_len); + if (crc != header->data_crc32) { + tb_sw_warn(sw, + "drom data crc32 mismatch (expected: %#x, got: %#x), aborting\n", + header->data_crc32, crc); + goto err; + } + + if (header->device_rom_revision > 1) + tb_sw_warn(sw, "drom device_rom_revision %#x unknown\n", + header->device_rom_revision); + + return tb_drom_parse_entries(sw); +err: + kfree(sw->drom); + return -EIO; + +} diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index c2a24b6fb883..9dfb8e18cdf7 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -400,7 +400,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; - if (tb_eeprom_read_uid(sw, &sw->uid)) + if (tb_drom_read_uid_only(sw, &sw->uid)) tb_sw_warn(sw, "could not read uid from eeprom\n"); else tb_sw_info(sw, "uid: %#llx\n", sw->uid); @@ -442,7 +442,7 @@ int tb_switch_resume(struct tb_switch *sw) u64 uid; tb_sw_info(sw, "resuming switch\n"); - err = tb_eeprom_read_uid(sw, &uid); + err = tb_drom_read_uid_only(sw, &uid); if (err) { tb_sw_warn(sw, "uid read failed\n"); return err; diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 63e89d01047c..18ade5e33ae9 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -22,6 +22,7 @@ struct tb_switch { u64 uid; int cap_plug_events; /* offset, zero if not found */ bool is_unplugged; /* unplugged, will go away */ + u8 *drom; }; /** @@ -33,6 +34,9 @@ struct tb_port { struct tb_port *remote; /* remote port, NULL if not connected */ int cap_phy; /* offset, zero if not found */ u8 port; /* port number on switch */ + bool disabled; /* disabled by eeprom */ + struct tb_port *dual_link_port; + u8 link_nr:1; }; /** @@ -237,7 +241,8 @@ int tb_path_activate(struct tb_path *path); void tb_path_deactivate(struct tb_path *path); bool tb_path_is_invalid(struct tb_path *path); -int tb_eeprom_read_uid(struct tb_switch *sw, u64 *uid); +int tb_drom_read(struct tb_switch *sw); +int tb_drom_read_uid_only(struct tb_switch *sw, u64 *uid); static inline int tb_route_length(u64 route) -- cgit v1.2.3 From 343fcb8c70d76967ba64493ca984e40baad9d0f6 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Thu, 12 Jun 2014 23:11:47 +0200 Subject: thunderbolt: Fix nontrivial endpoint devices. Fix issues observed with the Startech docking station: Fix the type of the route parameter in tb_ctl_rx. It should be u64 and not u8 (which only worked for short routes). A thunderbolt cable contains two lanes. If both endpoints support it a connection will be established on both lanes. Previously we tried to scan below both "dual link ports". Use the information extracted from the drom to only scan behind ports with lane_nr == 0. Endpoints with more complex thunderbolt controllers have some of their ports disabled (for example the NHI port or one of the HDMI/DP ports). Accessing them results in an error so we now ignore ports which are marked as disabled in the drom. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/ctl.c | 2 +- drivers/thunderbolt/switch.c | 42 +++++++++++++++++++++++++----------------- drivers/thunderbolt/tb.c | 5 +++++ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 9b0120bede51..d04fee4acb2e 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -439,7 +439,7 @@ rx: */ static struct tb_cfg_result tb_ctl_rx(struct tb_ctl *ctl, void *buffer, size_t length, int timeout_msec, - u8 route, enum tb_cfg_pkg_type type) + u64 route, enum tb_cfg_pkg_type type) { struct tb_cfg_result res; struct ctl_pkg *pkg; diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 9dfb8e18cdf7..0d50e7e7b29b 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -180,20 +180,17 @@ int tb_port_clear_counter(struct tb_port *port, int counter) * * Return: Returns 0 on success or an error code on failure. */ -static int tb_init_port(struct tb_switch *sw, u8 port_nr) +static int tb_init_port(struct tb_port *port) { int res; int cap; - struct tb_port *port = &sw->ports[port_nr]; - port->sw = sw; - port->port = port_nr; - port->remote = NULL; + res = tb_port_read(port, &port->config, TB_CFG_PORT, 0, 8); if (res) return res; /* Port 0 is the switch itself and has no PHY. */ - if (port->config.type == TB_TYPE_PORT && port_nr != 0) { + if (port->config.type == TB_TYPE_PORT && port->port != 0) { cap = tb_find_cap(port, TB_CFG_PORT, TB_CAP_PHY); if (cap > 0) @@ -202,7 +199,7 @@ static int tb_init_port(struct tb_switch *sw, u8 port_nr) tb_port_WARN(port, "non switch port without a PHY\n"); } - tb_dump_port(sw->tb, &port->config); + tb_dump_port(port->sw->tb, &port->config); /* TODO: Read dual link port, DP port and more from EEPROM. */ return 0; @@ -329,6 +326,7 @@ void tb_switch_free(struct tb_switch *sw) tb_plug_events_active(sw, false); kfree(sw->ports); + kfree(sw->drom); kfree(sw); } @@ -381,18 +379,16 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) /* initialize ports */ sw->ports = kcalloc(sw->config.max_port_number + 1, sizeof(*sw->ports), - GFP_KERNEL); + GFP_KERNEL); if (!sw->ports) goto err; for (i = 0; i <= sw->config.max_port_number; i++) { - if (tb_init_port(sw, i)) - goto err; - /* TODO: check if port is disabled (EEPROM) */ + /* minimum setup for tb_find_cap and tb_drom_read to work */ + sw->ports[i].sw = sw; + sw->ports[i].port = i; } - /* TODO: I2C, IECS, EEPROM, link controller */ - cap = tb_find_cap(&sw->ports[0], TB_CFG_SWITCH, TB_CAP_PLUG_EVENTS); if (cap < 0) { tb_sw_warn(sw, "cannot find TB_CAP_PLUG_EVENTS aborting\n"); @@ -400,10 +396,21 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) } sw->cap_plug_events = cap; - if (tb_drom_read_uid_only(sw, &sw->uid)) - tb_sw_warn(sw, "could not read uid from eeprom\n"); - else - tb_sw_info(sw, "uid: %#llx\n", sw->uid); + /* read drom */ + if (tb_drom_read(sw)) + tb_sw_warn(sw, "tb_eeprom_read_rom failed, continuing\n"); + tb_sw_info(sw, "uid: %#llx\n", sw->uid); + + for (i = 0; i <= sw->config.max_port_number; i++) { + if (sw->ports[i].disabled) { + tb_port_info(&sw->ports[i], "disabled by eeprom\n"); + continue; + } + if (tb_init_port(&sw->ports[i])) + goto err; + } + + /* TODO: I2C, IECS, link controller */ if (tb_plug_events_active(sw, true)) goto err; @@ -411,6 +418,7 @@ struct tb_switch *tb_switch_alloc(struct tb *tb, u64 route) return sw; err: kfree(sw->ports); + kfree(sw->drom); kfree(sw); return NULL; } diff --git a/drivers/thunderbolt/tb.c b/drivers/thunderbolt/tb.c index 1aa6dd7dc68b..d2c3fe346e91 100644 --- a/drivers/thunderbolt/tb.c +++ b/drivers/thunderbolt/tb.c @@ -38,6 +38,11 @@ static void tb_scan_port(struct tb_port *port) return; if (port->config.type != TB_TYPE_PORT) return; + if (port->dual_link_port && port->link_nr) + return; /* + * Downstream switch is reachable through two ports. + * Only scan on the primary port (link_nr == 0). + */ if (tb_wait_for_port(port, false) <= 0) return; if (port->remote) { -- cgit v1.2.3 From 7cbcb136a7d703f1bd590979a3d07348fe1bc81d Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Thu, 8 May 2014 11:56:37 +0400 Subject: w1: mxc_w1: Fix incorrect "presence" status W1 reset_bus() should return zero if slave device is present. This patch fix this issue. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/mxc_w1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 67b067a3e2ab..a5df5e89d456 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -66,7 +66,7 @@ static u8 mxc_w1_ds2_reset_bus(void *data) udelay(100); } - return !!(reg_val & MXC_W1_CONTROL_PST); + return !(reg_val & MXC_W1_CONTROL_PST); } /* -- cgit v1.2.3 From b0dceb6a96c2f87a40a9f99bc05711a3f7c1eaa2 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Thu, 8 May 2014 11:56:38 +0400 Subject: w1: mxc_w1: Optimize mxc_w1_ds2_reset_bus() According to the i.MX reference manual, the reset procedure and "presence" pulse takes 511 and 512 us, respectively. Measurement for i.MX27 is about 1100 us. There is no need to wait Reset+Presence more than this time. This patch optimizes mxc_w1_ds2_reset_bus() function to use proper value for delay after w1 bus reset. Nevertheless, a small margin for the timeout has been added for the case if clock frequency is inaccurate. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/mxc_w1.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index a5df5e89d456..0d05abe34362 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -15,16 +15,13 @@ #include #include #include +#include #include #include #include "../w1.h" #include "../w1_int.h" -/* According to the mx27 Datasheet the reset procedure should take up to about - * 1350us. We set the timeout to 500*100us = 50ms for sure */ -#define MXC_W1_RESET_TIMEOUT 500 - /* * MXC W1 Register offsets */ @@ -49,24 +46,25 @@ struct mxc_w1_device { */ static u8 mxc_w1_ds2_reset_bus(void *data) { - u8 reg_val; - unsigned int timeout_cnt = 0; struct mxc_w1_device *dev = data; + unsigned long timeout; - writeb(MXC_W1_CONTROL_RPP, (dev->regs + MXC_W1_CONTROL)); + writeb(MXC_W1_CONTROL_RPP, dev->regs + MXC_W1_CONTROL); - while (1) { - reg_val = readb(dev->regs + MXC_W1_CONTROL); + /* Wait for reset sequence 511+512us, use 1500us for sure */ + timeout = jiffies + usecs_to_jiffies(1500); - if (!(reg_val & MXC_W1_CONTROL_RPP) || - timeout_cnt > MXC_W1_RESET_TIMEOUT) - break; - else - timeout_cnt++; + udelay(511 + 512); - udelay(100); - } - return !(reg_val & MXC_W1_CONTROL_PST); + do { + u8 ctrl = readb(dev->regs + MXC_W1_CONTROL); + + /* PST bit is valid after the RPP bit is self-cleared */ + if (!(ctrl & MXC_W1_CONTROL_RPP)) + return !(ctrl & MXC_W1_CONTROL_PST); + } while (time_is_after_jiffies(timeout)); + + return 1; } /* -- cgit v1.2.3 From b7ce0b5d03f303cba0fff3f9df17f400d4e7a2d8 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Thu, 8 May 2014 11:56:39 +0400 Subject: w1: mxc_w1: Perform a software reset at startup This patch adds a software reset for 1-Wire module at driver startup. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/mxc_w1.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 0d05abe34362..741d2bb400ae 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -32,6 +32,7 @@ # define MXC_W1_CONTROL_RPP BIT(7) #define MXC_W1_TIME_DIVIDER 0x02 #define MXC_W1_RESET 0x04 +# define MXC_W1_RESET_RST BIT(0) struct mxc_w1_device { void __iomem *regs; @@ -129,6 +130,10 @@ static int mxc_w1_probe(struct platform_device *pdev) if (err) return err; + /* Software reset 1-Wire module */ + writeb(MXC_W1_RESET_RST, mdev->regs + MXC_W1_RESET); + writeb(0, mdev->regs + MXC_W1_RESET); + writeb(clkdiv - 1, mdev->regs + MXC_W1_TIME_DIVIDER); mdev->bus_master.data = mdev; -- cgit v1.2.3 From f80b2581a7068907076ca92c43866f36f446c039 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Thu, 8 May 2014 11:56:40 +0400 Subject: w1: mxc_w1: Optimize mxc_w1_ds2_touch_bit() According to the i.MX reference manual, the read/write bit operations takes from 60 us to 120 us. This patch optimizes mxc_w1_ds2_touch_bit() function to use proper value for such delay. Nevertheless, a small margin for the timeout has been added for the case if clock frequency is inaccurate. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/mxc_w1.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/drivers/w1/masters/mxc_w1.c b/drivers/w1/masters/mxc_w1.c index 741d2bb400ae..da3d0f0ad63c 100644 --- a/drivers/w1/masters/mxc_w1.c +++ b/drivers/w1/masters/mxc_w1.c @@ -75,22 +75,25 @@ static u8 mxc_w1_ds2_reset_bus(void *data) */ static u8 mxc_w1_ds2_touch_bit(void *data, u8 bit) { - struct mxc_w1_device *mdev = data; - void __iomem *ctrl_addr = mdev->regs + MXC_W1_CONTROL; - unsigned int timeout_cnt = 400; /* Takes max. 120us according to - * datasheet. - */ + struct mxc_w1_device *dev = data; + unsigned long timeout; + + writeb(MXC_W1_CONTROL_WR(bit), dev->regs + MXC_W1_CONTROL); + + /* Wait for read/write bit (60us, Max 120us), use 200us for sure */ + timeout = jiffies + usecs_to_jiffies(200); - writeb(MXC_W1_CONTROL_WR(bit), ctrl_addr); + udelay(60); - while (timeout_cnt--) { - if (!(readb(ctrl_addr) & MXC_W1_CONTROL_WR(bit))) - break; + do { + u8 ctrl = readb(dev->regs + MXC_W1_CONTROL); - udelay(1); - } + /* RDST bit is valid after the WR1/RD bit is self-cleared */ + if (!(ctrl & MXC_W1_CONTROL_WR(bit))) + return !!(ctrl & MXC_W1_CONTROL_RDST); + } while (time_is_after_jiffies(timeout)); - return !!(readb(ctrl_addr) & MXC_W1_CONTROL_RDST); + return 0; } static int mxc_w1_probe(struct platform_device *pdev) -- cgit v1.2.3 From 7242d42ace30825553eabd424b41b03136d37faa Mon Sep 17 00:00:00 2001 From: Thomas Wood Date: Fri, 30 May 2014 16:10:20 -0700 Subject: drivers/w1/w1_int.c: Fix style errors. Replace spaces at beginning of the string with tabs, and replace foo * bar with foo *bar in a pointer declaration. Signed-off-by: Thomas Wood Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1_int.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index 728039d2efe1..dfb6644028c0 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -38,7 +38,7 @@ module_param_named(search_count, w1_search_count, int, 0); static int w1_enable_pullup = 1; module_param_named(enable_pullup, w1_enable_pullup, int, 0); -static struct w1_master * w1_alloc_dev(u32 id, int slave_count, int slave_ttl, +static struct w1_master *w1_alloc_dev(u32 id, int slave_count, int slave_ttl, struct device_driver *driver, struct device *device) { @@ -116,13 +116,13 @@ int w1_add_master_device(struct w1_bus_master *master) struct w1_netlink_msg msg; int id, found; - /* validate minimum functionality */ - if (!(master->touch_bit && master->reset_bus) && - !(master->write_bit && master->read_bit) && + /* validate minimum functionality */ + if (!(master->touch_bit && master->reset_bus) && + !(master->write_bit && master->read_bit) && !(master->write_byte && master->read_byte && master->reset_bus)) { printk(KERN_ERR "w1_add_master_device: invalid function set\n"); return(-EINVAL); - } + } /* Lock until the device is added (or not) to w1_masters. */ mutex_lock(&w1_mlock); -- cgit v1.2.3 From 94859308a21b2bbf3d7c48789513c0c4a9ea7de1 Mon Sep 17 00:00:00 2001 From: Scott Alfter Date: Tue, 17 Jun 2014 20:42:05 +0000 Subject: w1: new w1_ds2406 driver Some preliminary work at making use of this driver led me to implement CRC-16 checks on read and write to deal with the occasional glitchiness of the 1-Wire bus. The revised driver (attached) returns an I/O error if the CRC check fails. When reading the chip's state, either you get a valid indication or you get an I/O error. When changing its state, either the change is successful or an I/O error is returned. Signed-off-by: Scott Alfter Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- Documentation/w1/slaves/w1_ds2406 | 25 ++++++ drivers/w1/slaves/Kconfig | 7 ++ drivers/w1/slaves/Makefile | 1 + drivers/w1/slaves/w1_ds2406.c | 168 ++++++++++++++++++++++++++++++++++++++ drivers/w1/w1_family.h | 1 + 5 files changed, 202 insertions(+) create mode 100644 Documentation/w1/slaves/w1_ds2406 create mode 100644 drivers/w1/slaves/w1_ds2406.c diff --git a/Documentation/w1/slaves/w1_ds2406 b/Documentation/w1/slaves/w1_ds2406 new file mode 100644 index 000000000000..8137fe6f6c3d --- /dev/null +++ b/Documentation/w1/slaves/w1_ds2406 @@ -0,0 +1,25 @@ +w1_ds2406 kernel driver +======================= + +Supported chips: + * Maxim DS2406 (and other family 0x12) addressable switches + +Author: Scott Alfter + +Description +----------- + +The w1_ds2406 driver allows connected devices to be switched on and off. +These chips also provide 128 bytes of OTP EPROM, but reading/writing it is +not supported. In TSOC-6 form, the DS2406 provides two switch outputs and +can be provided with power on a dedicated input. In TO-92 form, it provides +one output and uses parasitic power only. + +The driver provides two sysfs files. state is readable; it gives the +current state of each switch, with PIO A in bit 0 and PIO B in bit 1. The +driver ORs this state with 0x30, so shell scripts get an ASCII 0/1/2/3 to +work with. output is writable; bits 0 and 1 control PIO A and B, +respectively. Bits 2-7 are ignored, so it's safe to write ASCII data. + +CRCs are checked on read and write. Failed checks cause an I/O error to be +returned. On a failed write, the switch status is not changed. diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index 1cdce80b6abf..fd31d2f32c48 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -38,6 +38,13 @@ config W1_SLAVE_DS2413 Say Y here if you want to use a 1-wire DS2413 Dual Channel Addressable Switch device support +config W1_SLAVE_DS2406 + tristate "Dual Channel Addressable Switch 0x12 family support (DS2406)" + help + Say Y or M here if you want to use a 1-wire + DS2406 Dual Channel Addressable Switch. EPROM read/write + support for these devices is not implemented. + config W1_SLAVE_DS2423 tristate "Counter 1-wire device (DS2423)" select CRC16 diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile index 06529f3157ab..1e9989afe7bf 100644 --- a/drivers/w1/slaves/Makefile +++ b/drivers/w1/slaves/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_W1_SLAVE_THERM) += w1_therm.o obj-$(CONFIG_W1_SLAVE_SMEM) += w1_smem.o obj-$(CONFIG_W1_SLAVE_DS2408) += w1_ds2408.o obj-$(CONFIG_W1_SLAVE_DS2413) += w1_ds2413.o +obj-$(CONFIG_W1_SLAVE_DS2406) += w1_ds2406.o obj-$(CONFIG_W1_SLAVE_DS2423) += w1_ds2423.o obj-$(CONFIG_W1_SLAVE_DS2431) += w1_ds2431.o obj-$(CONFIG_W1_SLAVE_DS2433) += w1_ds2433.o diff --git a/drivers/w1/slaves/w1_ds2406.c b/drivers/w1/slaves/w1_ds2406.c new file mode 100644 index 000000000000..d488961a8c90 --- /dev/null +++ b/drivers/w1/slaves/w1_ds2406.c @@ -0,0 +1,168 @@ +/* + * w1_ds2406.c - w1 family 12 (DS2406) driver + * based on w1_ds2413.c by Mariusz Bialonczyk + * + * Copyright (c) 2014 Scott Alfter + * + * This source code is licensed under the GNU General Public License, + * Version 2. See the file COPYING for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../w1.h" +#include "../w1_int.h" +#include "../w1_family.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Scott Alfter "); +MODULE_DESCRIPTION("w1 family 12 driver for DS2406 2 Pin IO"); + +#define W1_F12_FUNC_READ_STATUS 0xAA +#define W1_F12_FUNC_WRITE_STATUS 0x55 + +static ssize_t w1_f12_read_state( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + u8 w1_buf[6]={W1_F12_FUNC_READ_STATUS, 7, 0, 0, 0, 0}; + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u16 crc=0; + int i; + ssize_t rtnval=1; + + if (off != 0) + return 0; + if (!buf) + return -EINVAL; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + w1_write_block(sl->master, w1_buf, 3); + w1_read_block(sl->master, w1_buf+3, 3); + for (i=0; i<6; i++) + crc=crc16_byte(crc, w1_buf[i]); + if (crc==0xb001) /* good read? */ + *buf=((w1_buf[3]>>5)&3)|0x30; + else + rtnval=-EIO; + + mutex_unlock(&sl->master->bus_mutex); + + return rtnval; +} + +static ssize_t w1_f12_write_output( + struct file *filp, struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t off, size_t count) +{ + struct w1_slave *sl = kobj_to_w1_slave(kobj); + u8 w1_buf[6]={W1_F12_FUNC_WRITE_STATUS, 7, 0, 0, 0, 0}; + u16 crc=0; + int i; + ssize_t rtnval=1; + + if (count != 1 || off != 0) + return -EFAULT; + + mutex_lock(&sl->master->bus_mutex); + + if (w1_reset_select_slave(sl)) { + mutex_unlock(&sl->master->bus_mutex); + return -EIO; + } + + w1_buf[3] = (((*buf)&3)<<5)|0x1F; + w1_write_block(sl->master, w1_buf, 4); + w1_read_block(sl->master, w1_buf+4, 2); + for (i=0; i<6; i++) + crc=crc16_byte(crc, w1_buf[i]); + if (crc==0xb001) /* good read? */ + w1_write_8(sl->master, 0xFF); + else + rtnval=-EIO; + + mutex_unlock(&sl->master->bus_mutex); + return rtnval; +} + +#define NB_SYSFS_BIN_FILES 2 +static struct bin_attribute w1_f12_sysfs_bin_files[NB_SYSFS_BIN_FILES] = { + { + .attr = { + .name = "state", + .mode = S_IRUGO, + }, + .size = 1, + .read = w1_f12_read_state, + }, + { + .attr = { + .name = "output", + .mode = S_IRUGO | S_IWUSR | S_IWGRP, + }, + .size = 1, + .write = w1_f12_write_output, + } +}; + +static int w1_f12_add_slave(struct w1_slave *sl) +{ + int err = 0; + int i; + + for (i = 0; i < NB_SYSFS_BIN_FILES && !err; ++i) + err = sysfs_create_bin_file( + &sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); + if (err) + while (--i >= 0) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); + return err; +} + +static void w1_f12_remove_slave(struct w1_slave *sl) +{ + int i; + for (i = NB_SYSFS_BIN_FILES - 1; i >= 0; --i) + sysfs_remove_bin_file(&sl->dev.kobj, + &(w1_f12_sysfs_bin_files[i])); +} + +static struct w1_family_ops w1_f12_fops = { + .add_slave = w1_f12_add_slave, + .remove_slave = w1_f12_remove_slave, +}; + +static struct w1_family w1_family_12 = { + .fid = W1_FAMILY_DS2406, + .fops = &w1_f12_fops, +}; + +static int __init w1_f12_init(void) +{ + return w1_register_family(&w1_family_12); +} + +static void __exit w1_f12_exit(void) +{ + w1_unregister_family(&w1_family_12); +} + +module_init(w1_f12_init); +module_exit(w1_f12_exit); diff --git a/drivers/w1/w1_family.h b/drivers/w1/w1_family.h index 26ca1343055b..0d18365b61ad 100644 --- a/drivers/w1/w1_family.h +++ b/drivers/w1/w1_family.h @@ -40,6 +40,7 @@ #define W1_FAMILY_DS2760 0x30 #define W1_FAMILY_DS2780 0x32 #define W1_FAMILY_DS2413 0x3A +#define W1_FAMILY_DS2406 0x12 #define W1_THERM_DS1825 0x3B #define W1_FAMILY_DS2781 0x3D #define W1_THERM_DS28EA00 0x42 -- cgit v1.2.3 From ea022eac1e297bbf725c1e484726b4c8fb09dd80 Mon Sep 17 00:00:00 2001 From: Fjodor Schelichow Date: Thu, 19 Jun 2014 02:51:59 +0200 Subject: w1/slaves: use pr_* instead of printk This patch replaces all calls to the "printk" function within the "slaves" subdirectory by calls to the appropriate "pr_*" function thus addressing the following warning generated by the checkpatch script: WARNING: Prefer [subsystem eg: netdev]_err([subsystem]dev, ... then dev_err(dev, ... then pr_err(... to printk(KERN_ERR ... Signed-off-by: Fjodor Schelichow Signed-off-by: Roman Sommer Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/w1_ds2760.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/w1/slaves/w1_ds2760.c b/drivers/w1/slaves/w1_ds2760.c index 65f90dccd60e..d9079d48d112 100644 --- a/drivers/w1/slaves/w1_ds2760.c +++ b/drivers/w1/slaves/w1_ds2760.c @@ -181,8 +181,7 @@ static struct w1_family w1_ds2760_family = { static int __init w1_ds2760_init(void) { - printk(KERN_INFO "1-Wire driver for the DS2760 battery monitor " - " chip - (c) 2004-2005, Szabolcs Gyurko\n"); + pr_info("1-Wire driver for the DS2760 battery monitor chip - (c) 2004-2005, Szabolcs Gyurko\n"); ida_init(&bat_ida); return w1_register_family(&w1_ds2760_family); } -- cgit v1.2.3 From fdc9167a7853523647ed0b19d719256c56f1f685 Mon Sep 17 00:00:00 2001 From: Fjodor Schelichow Date: Thu, 19 Jun 2014 02:52:00 +0200 Subject: w1: use pr_* instead of printk This patch replaces all calls to the "printk" function within the main "w1" directory by calls to the appropriate "pr_*" function thus addressing the following warning generated by the checkpatch script: WARNING: Prefer [subsystem eg: netdev]_err([subsystem]dev, ... then dev_err(dev, ... then pr_err(... to printk(KERN_ERR ... Signed-off-by: Fjodor Schelichow Signed-off-by: Roman Sommer Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/w1.c | 10 ++++------ drivers/w1/w1_family.c | 2 +- drivers/w1/w1_int.c | 9 ++++----- drivers/w1/w1_log.h | 4 ++-- drivers/w1/w1_netlink.c | 3 +-- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c index 5d7341520544..592f7edc671e 100644 --- a/drivers/w1/w1.c +++ b/drivers/w1/w1.c @@ -1162,28 +1162,26 @@ static int __init w1_init(void) { int retval; - printk(KERN_INFO "Driver for 1-wire Dallas network protocol.\n"); + pr_info("Driver for 1-wire Dallas network protocol.\n"); w1_init_netlink(); retval = bus_register(&w1_bus_type); if (retval) { - printk(KERN_ERR "Failed to register bus. err=%d.\n", retval); + pr_err("Failed to register bus. err=%d.\n", retval); goto err_out_exit_init; } retval = driver_register(&w1_master_driver); if (retval) { - printk(KERN_ERR - "Failed to register master driver. err=%d.\n", + pr_err("Failed to register master driver. err=%d.\n", retval); goto err_out_bus_unregister; } retval = driver_register(&w1_slave_driver); if (retval) { - printk(KERN_ERR - "Failed to register slave driver. err=%d.\n", + pr_err("Failed to register slave driver. err=%d.\n", retval); goto err_out_master_unregister; } diff --git a/drivers/w1/w1_family.c b/drivers/w1/w1_family.c index 3651ec801f45..1dc3051f7d76 100644 --- a/drivers/w1/w1_family.c +++ b/drivers/w1/w1_family.c @@ -87,7 +87,7 @@ void w1_unregister_family(struct w1_family *fent) w1_reconnect_slaves(fent, 0); while (atomic_read(&fent->refcnt)) { - printk(KERN_INFO "Waiting for family %u to become free: refcnt=%d.\n", + pr_info("Waiting for family %u to become free: refcnt=%d.\n", fent->fid, atomic_read(&fent->refcnt)); if (msleep_interruptible(1000)) diff --git a/drivers/w1/w1_int.c b/drivers/w1/w1_int.c index dfb6644028c0..47249a30eae3 100644 --- a/drivers/w1/w1_int.c +++ b/drivers/w1/w1_int.c @@ -50,8 +50,7 @@ static struct w1_master *w1_alloc_dev(u32 id, int slave_count, int slave_ttl, */ dev = kzalloc(sizeof(struct w1_master) + sizeof(struct w1_bus_master), GFP_KERNEL); if (!dev) { - printk(KERN_ERR - "Failed to allocate %zd bytes for new w1 device.\n", + pr_err("Failed to allocate %zd bytes for new w1 device.\n", sizeof(struct w1_master)); return NULL; } @@ -91,7 +90,7 @@ static struct w1_master *w1_alloc_dev(u32 id, int slave_count, int slave_ttl, err = device_register(&dev->dev); if (err) { - printk(KERN_ERR "Failed to register master device. err=%d\n", err); + pr_err("Failed to register master device. err=%d\n", err); memset(dev, 0, sizeof(struct w1_master)); kfree(dev); dev = NULL; @@ -120,7 +119,7 @@ int w1_add_master_device(struct w1_bus_master *master) if (!(master->touch_bit && master->reset_bus) && !(master->write_bit && master->read_bit) && !(master->write_byte && master->read_byte && master->reset_bus)) { - printk(KERN_ERR "w1_add_master_device: invalid function set\n"); + pr_err("w1_add_master_device: invalid function set\n"); return(-EINVAL); } @@ -254,7 +253,7 @@ void w1_remove_master_device(struct w1_bus_master *bm) } if (!found) { - printk(KERN_ERR "Device doesn't exist.\n"); + pr_err("Device doesn't exist.\n"); return; } diff --git a/drivers/w1/w1_log.h b/drivers/w1/w1_log.h index 9c7bd62e6bdc..f9eecff23b8d 100644 --- a/drivers/w1/w1_log.h +++ b/drivers/w1/w1_log.h @@ -29,8 +29,8 @@ #else # define assert(expr) \ if(unlikely(!(expr))) { \ - printk(KERN_ERR "Assertion failed! %s,%s,%s,line=%d\n", \ - #expr, __FILE__, __func__, __LINE__); \ + pr_err("Assertion failed! %s,%s,%s,line=%d\n", \ + #expr, __FILE__, __func__, __LINE__); \ } #endif diff --git a/drivers/w1/w1_netlink.c b/drivers/w1/w1_netlink.c index 351a2978ba72..dd9656237274 100644 --- a/drivers/w1/w1_netlink.c +++ b/drivers/w1/w1_netlink.c @@ -680,8 +680,7 @@ static void w1_cn_callback(struct cn_msg *cn, struct netlink_skb_parms *nsp) if (sl) dev = sl->master; } else { - printk(KERN_NOTICE - "%s: cn: %x.%x, wrong type: %u, len: %u.\n", + pr_notice("%s: cn: %x.%x, wrong type: %u, len: %u.\n", __func__, cn->id.idx, cn->id.val, msg->type, msg->len); err = -EPROTO; -- cgit v1.2.3 From 1fda5690906b20ce823964eaac32baa8d3a03f61 Mon Sep 17 00:00:00 2001 From: Fjodor Schelichow Date: Thu, 19 Jun 2014 02:52:01 +0200 Subject: w1/masters: use pr_* instead of printk This patch replaces all calls to the "printk" function within the "masters" subdirectory by calls to the appropriate "pr_*" function thus addressing the following warning generated by the checkpatch script: WARNING: Prefer [subsystem eg: netdev]_err([subsystem]dev, ... then dev_err(dev, ... then pr_err(... to printk(KERN_ERR ... Signed-off-by: Fjodor Schelichow Signed-off-by: Roman Sommer Acked-by: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/masters/ds1wm.c | 2 +- drivers/w1/masters/ds2482.c | 2 +- drivers/w1/masters/ds2490.c | 50 ++++++++++++++++++++++----------------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/drivers/w1/masters/ds1wm.c b/drivers/w1/masters/ds1wm.c index 02df3b1381d2..e0b8a4bc73df 100644 --- a/drivers/w1/masters/ds1wm.c +++ b/drivers/w1/masters/ds1wm.c @@ -563,7 +563,7 @@ static struct platform_driver ds1wm_driver = { static int __init ds1wm_init(void) { - printk("DS1WM w1 busmaster driver - (c) 2004 Szabolcs Gyurko\n"); + pr_info("DS1WM w1 busmaster driver - (c) 2004 Szabolcs Gyurko\n"); return platform_driver_register(&ds1wm_driver); } diff --git a/drivers/w1/masters/ds2482.c b/drivers/w1/masters/ds2482.c index e033491fe308..e76a9b39abb2 100644 --- a/drivers/w1/masters/ds2482.c +++ b/drivers/w1/masters/ds2482.c @@ -226,7 +226,7 @@ static int ds2482_wait_1wire_idle(struct ds2482_data *pdev) } if (retries >= DS2482_WAIT_IDLE_TIMEOUT) - printk(KERN_ERR "%s: timeout on channel %d\n", + pr_err("%s: timeout on channel %d\n", __func__, pdev->channel); return temp; diff --git a/drivers/w1/masters/ds2490.c b/drivers/w1/masters/ds2490.c index 7404ad3062b7..1de6df87bfa3 100644 --- a/drivers/w1/masters/ds2490.c +++ b/drivers/w1/masters/ds2490.c @@ -206,7 +206,7 @@ static int ds_send_control_cmd(struct ds_device *dev, u16 value, u16 index) err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]), CONTROL_CMD, VENDOR, value, index, NULL, 0, 1000); if (err < 0) { - printk(KERN_ERR "Failed to send command control message %x.%x: err=%d.\n", + pr_err("Failed to send command control message %x.%x: err=%d.\n", value, index, err); return err; } @@ -221,7 +221,7 @@ static int ds_send_control_mode(struct ds_device *dev, u16 value, u16 index) err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]), MODE_CMD, VENDOR, value, index, NULL, 0, 1000); if (err < 0) { - printk(KERN_ERR "Failed to send mode control message %x.%x: err=%d.\n", + pr_err("Failed to send mode control message %x.%x: err=%d.\n", value, index, err); return err; } @@ -236,7 +236,7 @@ static int ds_send_control(struct ds_device *dev, u16 value, u16 index) err = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, dev->ep[EP_CONTROL]), COMM_CMD, VENDOR, value, index, NULL, 0, 1000); if (err < 0) { - printk(KERN_ERR "Failed to send control message %x.%x: err=%d.\n", + pr_err("Failed to send control message %x.%x: err=%d.\n", value, index, err); return err; } @@ -255,7 +255,8 @@ static int ds_recv_status_nodump(struct ds_device *dev, struct ds_status *st, err = usb_interrupt_msg(dev->udev, usb_rcvintpipe(dev->udev, dev->ep[EP_STATUS]), buf, size, &count, 100); if (err < 0) { - printk(KERN_ERR "Failed to read 1-wire data from 0x%x: err=%d.\n", dev->ep[EP_STATUS], err); + pr_err("Failed to read 1-wire data from 0x%x: err=%d.\n", + dev->ep[EP_STATUS], err); return err; } @@ -267,17 +268,17 @@ static int ds_recv_status_nodump(struct ds_device *dev, struct ds_status *st, static inline void ds_print_msg(unsigned char *buf, unsigned char *str, int off) { - printk(KERN_INFO "%45s: %8x\n", str, buf[off]); + pr_info("%45s: %8x\n", str, buf[off]); } static void ds_dump_status(struct ds_device *dev, unsigned char *buf, int count) { int i; - printk(KERN_INFO "0x%x: count=%d, status: ", dev->ep[EP_STATUS], count); + pr_info("0x%x: count=%d, status: ", dev->ep[EP_STATUS], count); for (i=0; i= 16) { ds_print_msg(buf, "enable flag", 0); @@ -305,21 +306,21 @@ static void ds_dump_status(struct ds_device *dev, unsigned char *buf, int count) } ds_print_msg(buf, "Result Register Value: ", i); if (buf[i] & RR_NRS) - printk(KERN_INFO "NRS: Reset no presence or ...\n"); + pr_info("NRS: Reset no presence or ...\n"); if (buf[i] & RR_SH) - printk(KERN_INFO "SH: short on reset or set path\n"); + pr_info("SH: short on reset or set path\n"); if (buf[i] & RR_APP) - printk(KERN_INFO "APP: alarming presence on reset\n"); + pr_info("APP: alarming presence on reset\n"); if (buf[i] & RR_VPP) - printk(KERN_INFO "VPP: 12V expected not seen\n"); + pr_info("VPP: 12V expected not seen\n"); if (buf[i] & RR_CMP) - printk(KERN_INFO "CMP: compare error\n"); + pr_info("CMP: compare error\n"); if (buf[i] & RR_CRC) - printk(KERN_INFO "CRC: CRC error detected\n"); + pr_info("CRC: CRC error detected\n"); if (buf[i] & RR_RDP) - printk(KERN_INFO "RDP: redirected page\n"); + pr_info("RDP: redirected page\n"); if (buf[i] & RR_EOS) - printk(KERN_INFO "EOS: end of search error\n"); + pr_info("EOS: end of search error\n"); } } @@ -330,15 +331,13 @@ static void ds_reset_device(struct ds_device *dev) * the strong pullup. */ if (ds_send_control_mode(dev, MOD_PULSE_EN, PULSE_SPUE)) - printk(KERN_ERR "ds_reset_device: " - "Error allowing strong pullup\n"); + pr_err("ds_reset_device: Error allowing strong pullup\n"); /* Chip strong pullup time was cleared. */ if (dev->spu_sleep) { /* lower 4 bits are 0, see ds_set_pullup */ u8 del = dev->spu_sleep>>4; if (ds_send_control(dev, COMM_SET_DURATION | COMM_IM, del)) - printk(KERN_ERR "ds_reset_device: " - "Error setting duration\n"); + pr_err("ds_reset_device: Error setting duration\n"); } } @@ -363,7 +362,7 @@ static int ds_recv_data(struct ds_device *dev, unsigned char *buf, int size) u8 buf[ST_SIZE]; int count; - printk(KERN_INFO "Clearing ep0x%x.\n", dev->ep[EP_DATA_IN]); + pr_info("Clearing ep0x%x.\n", dev->ep[EP_DATA_IN]); usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->ep[EP_DATA_IN])); count = ds_recv_status_nodump(dev, &st, buf, sizeof(buf)); @@ -391,7 +390,7 @@ static int ds_send_data(struct ds_device *dev, unsigned char *buf, int len) count = 0; err = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->ep[EP_DATA_OUT]), buf, len, &count, 1000); if (err < 0) { - printk(KERN_ERR "Failed to write 1-wire data to ep0x%x: " + pr_err("Failed to write 1-wire data to ep0x%x: " "err=%d.\n", dev->ep[EP_DATA_OUT], err); return err; } @@ -475,7 +474,7 @@ static int ds_wait_status(struct ds_device *dev, struct ds_status *st) } while (!(st->status & ST_IDLE) && !(err < 0) && ++count < 100); if (err >= 16 && st->status & ST_EPOF) { - printk(KERN_INFO "Resetting device after ST_EPOF.\n"); + pr_info("Resetting device after ST_EPOF.\n"); ds_reset_device(dev); /* Always dump the device status. */ count = 101; @@ -992,7 +991,7 @@ static int ds_probe(struct usb_interface *intf, dev = kzalloc(sizeof(struct ds_device), GFP_KERNEL); if (!dev) { - printk(KERN_INFO "Failed to allocate new DS9490R structure.\n"); + pr_info("Failed to allocate new DS9490R structure.\n"); return -ENOMEM; } dev->udev = usb_get_dev(udev); @@ -1024,7 +1023,8 @@ static int ds_probe(struct usb_interface *intf, iface_desc = &intf->altsetting[alt]; if (iface_desc->desc.bNumEndpoints != NUM_EP-1) { - printk(KERN_INFO "Num endpoints=%d. It is not DS9490R.\n", iface_desc->desc.bNumEndpoints); + pr_info("Num endpoints=%d. It is not DS9490R.\n", + iface_desc->desc.bNumEndpoints); err = -EINVAL; goto err_out_clear; } -- cgit v1.2.3 From 2b35404ef7762af15ce138281c91b4cc0e2d0124 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:29 +0530 Subject: thunderbolt: Fix build error in eeprom.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the below error: drivers/thunderbolt/eeprom.c:407:2: error: implicit declaration of function ‘kzalloc’ [-Werror=implicit-function-declaration] drivers/thunderbolt/eeprom.c:444:2: error: implicit declaration of function ‘kfree’ [-Werror=implicit-function-declaration] Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/eeprom.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 0d5a80b2d07a..bc0449f581c2 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -5,6 +5,7 @@ */ #include +#include #include "tb.h" /** -- cgit v1.2.3 From 10fefe56bba413fb0525207c65cf50cf2a5afaff Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:30 +0530 Subject: thunderbolt: Fix build error in switch.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the below error: drivers/thunderbolt/switch.c:347:2: error: implicit declaration of function ‘kzalloc’ [-Werror=implicit-function-declaration] drivers/thunderbolt/switch.c:381:2: error: implicit declaration of function ‘kcalloc’ [-Werror=implicit-function-declaration] Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 0d50e7e7b29b..26e76e4aa835 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -5,6 +5,7 @@ */ #include +#include #include "tb.h" -- cgit v1.2.3 From c9c2deef457c766a33c4862c9c198c20854d5fb6 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:31 +0530 Subject: thunderbolt: Use NULL instead of 0 in switch.c The function returns a pointer. Hence return NULL instead of 0. Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/switch.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/thunderbolt/switch.c b/drivers/thunderbolt/switch.c index 26e76e4aa835..aeb982969629 100644 --- a/drivers/thunderbolt/switch.c +++ b/drivers/thunderbolt/switch.c @@ -260,11 +260,11 @@ struct tb_switch *get_switch_at_route(struct tb_switch *sw, u64 route) if (route == 0) return sw; if (next_port > sw->config.max_port_number) - return 0; + return NULL; if (tb_is_upstream_port(&sw->ports[next_port])) - return 0; + return NULL; if (!sw->ports[next_port].remote) - return 0; + return NULL; return get_switch_at_route(sw->ports[next_port].remote->sw, route >> TB_ROUTE_SHIFT); } -- cgit v1.2.3 From 8db353bdd0975c220a62091f4fd28966478550fb Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:32 +0530 Subject: thunderbolt: Use NULL instead of 0 in ctl.c The function returns a pointer. Hence return NULL instead of 0. Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/ctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index d04fee4acb2e..4c6da92edcb4 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -305,13 +305,13 @@ static struct ctl_pkg *tb_ctl_pkg_alloc(struct tb_ctl *ctl) { struct ctl_pkg *pkg = kzalloc(sizeof(*pkg), GFP_KERNEL); if (!pkg) - return 0; + return NULL; pkg->ctl = ctl; pkg->buffer = dma_pool_alloc(ctl->frame_pool, GFP_KERNEL, &pkg->frame.buffer_phy); if (!pkg->buffer) { kfree(pkg); - return 0; + return NULL; } return pkg; } -- cgit v1.2.3 From f19b72c6e8bb0bc257d09da6e324841d27a68528 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:33 +0530 Subject: thunderbolt: Use NULL instead of 0 in nhi.c 'descriptors' is a pointer. Use NULL isntead of 0. Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 346b41e7d5d1..0fc137af89f5 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -416,7 +416,7 @@ void ring_free(struct tb_ring *ring) ring->size * sizeof(*ring->descriptors), ring->descriptors, ring->descriptors_dma); - ring->descriptors = 0; + ring->descriptors = NULL; ring->descriptors_dma = 0; -- cgit v1.2.3 From 620863f71c46509e104729c75a199689e59cac47 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 20 Jun 2014 14:32:34 +0530 Subject: thunderbolt: Staticize nhi_ids 'nhi_ids' is local to this file. Signed-off-by: Sachin Kamat Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 0fc137af89f5..2054fbf8b382 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -632,7 +632,7 @@ static const struct dev_pm_ops nhi_pm_ops = { .restore_noirq = nhi_resume_noirq, }; -struct pci_device_id nhi_ids[] = { +static struct pci_device_id nhi_ids[] = { /* * We have to specify class, the TB bridges use the same device and * vendor (sub)id. -- cgit v1.2.3 From 0cb4e2be8bce5e176021a2e96b38e5d3727645a4 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 20 Jun 2014 15:52:09 +0200 Subject: thunderbolt: add PCI dependency The thunderbolt drivers cannot be built if CONFIG_PCI is disabled, better add an explicit Kconfig dependency. The "default no" line is redundant and can be removed at the same time. Signed-off-by: Arnd Bergmann Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 3a2552962d06..5aab79bf3450 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -1,6 +1,6 @@ menuconfig THUNDERBOLT tristate "Thunderbolt support for Apple devices" - default no + depends on PCI help Cactus Ridge Thunderbolt Controller driver This driver is required if you want to hotplug Thunderbolt devices on -- cgit v1.2.3 From 3543fb776d8e63934615bf7070ee5fa5a6a7382d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Fri, 20 Jun 2014 15:52:11 +0200 Subject: thunderbolt: fix format string for size_t The result of "sizeof(struct tb_drom_entry_port)" is a size_t, which is not necessarily the same as 'long', so we should use the appropriate %z format string instead of %l. Signed-off-by: Arnd Bergmann Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/eeprom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index bc0449f581c2..b133f3fdaf51 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -323,7 +323,7 @@ static int tb_drom_parse_entry(struct tb_switch *sw, struct tb_drom_entry_port *entry = (void *) header; if (header->len != sizeof(*entry)) { tb_sw_warn(sw, - "port entry has size %#x (expected %#lx)\n", + "port entry has size %#x (expected %#zx)\n", header->len, sizeof(struct tb_drom_entry_port)); return -EIO; } -- cgit v1.2.3 From 801dba53fef8bfc2f1424c33914a41810594bde2 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Fri, 20 Jun 2014 21:42:22 +0200 Subject: thunderbolt: Add casts to prevent endianness warnings Thunderbolt packets are big endian. Cast pkg->buffer to __be32* when accessing the checksum. Reported-by: kbuild test robot Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/ctl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/ctl.c b/drivers/thunderbolt/ctl.c index 4c6da92edcb4..799634b382c6 100644 --- a/drivers/thunderbolt/ctl.c +++ b/drivers/thunderbolt/ctl.c @@ -355,7 +355,7 @@ static int tb_ctl_tx(struct tb_ctl *ctl, void *data, size_t len, pkg->frame.sof = type; pkg->frame.eof = type; cpu_to_be32_array(pkg->buffer, data, len / 4); - *(u32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len); + *(__be32 *) (pkg->buffer + len) = tb_crc(pkg->buffer, len); res = ring_tx(ctl->tx, &pkg->frame); if (res) /* ring is stopped */ @@ -412,7 +412,7 @@ static void tb_ctl_rx_callback(struct tb_ring *ring, struct ring_frame *frame, } frame->size -= 4; /* remove checksum */ - if (*(u32 *) (pkg->buffer + frame->size) + if (*(__be32 *) (pkg->buffer + frame->size) != tb_crc(pkg->buffer, frame->size)) { tb_ctl_err(pkg->ctl, "RX: checksum mismatch, dropping packet\n"); -- cgit v1.2.3 From 7f2d5f7bc529114c2e67520427be6ebac694e78f Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Fri, 20 Jun 2014 21:42:23 +0200 Subject: thunderbolt: Fix header declaration of tb_find_cap tb_find_cap in cap.c takes an enum tb_cap and not an u32. Fix the declaration in tb.h. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/tb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/tb.h b/drivers/thunderbolt/tb.h index 18ade5e33ae9..8b0d7cf2b6d6 100644 --- a/drivers/thunderbolt/tb.h +++ b/drivers/thunderbolt/tb.h @@ -233,7 +233,7 @@ int tb_wait_for_port(struct tb_port *port, bool wait_if_unplugged); int tb_port_add_nfc_credits(struct tb_port *port, int credits); int tb_port_clear_counter(struct tb_port *port, int counter); -int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, u32 value); +int tb_find_cap(struct tb_port *port, enum tb_cfg_space space, enum tb_cap cap); struct tb_path *tb_path_alloc(struct tb *tb, int num_hops); void tb_path_free(struct tb_path *path); -- cgit v1.2.3 From e7120778a4518a1c8f188ef9865058f7f5a36919 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Fri, 20 Jun 2014 21:42:24 +0200 Subject: thunderbolt: Make enum tb_drom_entry_type unsigned Force enum tb_drom_entry_type to unsigned to fix the following error: drivers/thunderbolt/eeprom.c:202:39: error: dubious one-bit signed bitfield Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/eeprom.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index b133f3fdaf51..71f719b67115 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -191,7 +191,8 @@ struct tb_drom_header { } __packed; enum tb_drom_entry_type { - TB_DROM_ENTRY_GENERIC, + /* force unsigned to prevent "one-bit signed bitfield" warning */ + TB_DROM_ENTRY_GENERIC = 0U, TB_DROM_ENTRY_PORT, }; -- cgit v1.2.3 From e0f550141be3d4c401ae15a0cd1877d4d9665f16 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Fri, 20 Jun 2014 21:42:25 +0200 Subject: thunderbolt: Make tb_eeprom_get_drom_offset static tb_eeprom_get_drom_offset is local to this file. Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/eeprom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/thunderbolt/eeprom.c b/drivers/thunderbolt/eeprom.c index 71f719b67115..0dde34e3a7c5 100644 --- a/drivers/thunderbolt/eeprom.c +++ b/drivers/thunderbolt/eeprom.c @@ -233,7 +233,7 @@ struct tb_drom_entry_port { /** * tb_eeprom_get_drom_offset - get drom offset within eeprom */ -int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) +static int tb_eeprom_get_drom_offset(struct tb_switch *sw, u16 *offset) { struct tb_cap_plug_events cap; int res; -- cgit v1.2.3 From f34323b64ac6bb475e158f93c08ee677ecffe8a3 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Sat, 21 Jun 2014 12:15:44 +0200 Subject: thunderbolt: select CRC32 in Kconfig We use __crc32c_le in ctl.c. So make sure that the dependency is there. Reported-by: kbuild test robot Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/thunderbolt/Kconfig b/drivers/thunderbolt/Kconfig index 5aab79bf3450..c121acc15bfe 100644 --- a/drivers/thunderbolt/Kconfig +++ b/drivers/thunderbolt/Kconfig @@ -1,6 +1,7 @@ menuconfig THUNDERBOLT tristate "Thunderbolt support for Apple devices" depends on PCI + select CRC32 help Cactus Ridge Thunderbolt Controller driver This driver is required if you want to hotplug Thunderbolt devices on -- cgit v1.2.3 From 6b5fa77608352f05769ca4e10ec3a32d1446b4a6 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 23 Jun 2014 16:31:47 +0200 Subject: w1: select crc16 library for ds2406 commit 94859308a21b "w1: new w1_ds2406 driver" added a new driver that uses the crc16 library, but didn't ensure that the core is there. This adds the necessary Kconfig statements, just like we have it for other w1 drivers. Reported-by: kbuild test robot Signed-off-by: Arnd Bergmann Acked-by: Scott Alfter Cc: Evgeniy Polyakov Signed-off-by: Greg Kroah-Hartman --- drivers/w1/slaves/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig index fd31d2f32c48..cfe74d09932e 100644 --- a/drivers/w1/slaves/Kconfig +++ b/drivers/w1/slaves/Kconfig @@ -40,6 +40,7 @@ config W1_SLAVE_DS2413 config W1_SLAVE_DS2406 tristate "Dual Channel Addressable Switch 0x12 family support (DS2406)" + select CRC16 help Say Y or M here if you want to use a 1-wire DS2406 Dual Channel Addressable Switch. EPROM read/write -- cgit v1.2.3 From fc51768ba24077c8148067036e1555a8a978bb99 Mon Sep 17 00:00:00 2001 From: Himangi Saraogi Date: Sun, 6 Jul 2014 21:43:42 +0530 Subject: thunderbolt: Correct the size argument to devm_kzalloc nhi->rx_rings does not have type as struct tb_ring *, as it is a double pointer so the elements of the array should have pointer type, not structure type. The Coccinelle semantic patch that makes this change is as follows: // @disable sizeof_type_expr@ type T; T **x; @@ x = <+...sizeof( - T + *x )...+> // Signed-off-by: Himangi Saraogi Acked-by: Julia Lawall Cc: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index 2054fbf8b382..ce72f31fe0d8 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -570,10 +570,10 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); nhi->tx_rings = devm_kzalloc(&pdev->dev, - nhi->hop_count * sizeof(struct tb_ring), + nhi->hop_count * sizeof(*nhi->tx_rings), GFP_KERNEL); nhi->rx_rings = devm_kzalloc(&pdev->dev, - nhi->hop_count * sizeof(struct tb_ring), + nhi->hop_count * sizeof(*nhi->rx_rings), GFP_KERNEL); if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; -- cgit v1.2.3 From c1f732ad767e37bd1d41043cbdefc0874b4d05e5 Mon Sep 17 00:00:00 2001 From: Kleber Sacilotto de Souza Date: Wed, 4 Jun 2014 10:57:50 -0300 Subject: GenWQE: Add sysfs interface for bitstream reload This patch adds an interface on sysfs for userspace to request a card bitstream reload. It sets the appropriate register and try to perform a fundamental reset on the PCIe slot for the card to reload the bitstream from the chosen partition. Signed-off-by: Kleber Sacilotto de Souza Acked-by: Frank Haverkamp Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-driver-genwqe | 9 +++ drivers/misc/genwqe/card_base.c | 90 +++++++++++++++++++++++++++ drivers/misc/genwqe/card_sysfs.c | 25 ++++++++ include/uapi/linux/genwqe/genwqe_card.h | 1 + 4 files changed, 125 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-driver-genwqe b/Documentation/ABI/testing/sysfs-driver-genwqe index 1870737a1f5e..64ac6d567c4b 100644 --- a/Documentation/ABI/testing/sysfs-driver-genwqe +++ b/Documentation/ABI/testing/sysfs-driver-genwqe @@ -25,6 +25,15 @@ Date: Oct 2013 Contact: haver@linux.vnet.ibm.com Description: Interface to set the next bitstream to be used. +What: /sys/class/genwqe/genwqe_card/reload_bitstream +Date: May 2014 +Contact: klebers@linux.vnet.ibm.com +Description: Interface to trigger a PCIe card reset to reload the bitstream. + sudo sh -c 'echo 1 > \ + /sys/class/genwqe/genwqe0_card/reload_bitstream' + If successfully, the card will come back with the bitstream set + on 'next_bitstream'. + What: /sys/class/genwqe/genwqe_card/tempsens Date: Oct 2013 Contact: haver@linux.vnet.ibm.com diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c index 74d51c9bb858..e6cc3e1e7326 100644 --- a/drivers/misc/genwqe/card_base.c +++ b/drivers/misc/genwqe/card_base.c @@ -760,6 +760,89 @@ static u64 genwqe_fir_checking(struct genwqe_dev *cd) return IO_ILLEGAL_VALUE; } +/** + * genwqe_pci_fundamental_reset() - trigger a PCIe fundamental reset on the slot + * + * Note: pci_set_pcie_reset_state() is not implemented on all archs, so this + * reset method will not work in all cases. + * + * Return: 0 on success or error code from pci_set_pcie_reset_state() + */ +static int genwqe_pci_fundamental_reset(struct pci_dev *pci_dev) +{ + int rc; + + /* + * lock pci config space access from userspace, + * save state and issue PCIe fundamental reset + */ + pci_cfg_access_lock(pci_dev); + pci_save_state(pci_dev); + rc = pci_set_pcie_reset_state(pci_dev, pcie_warm_reset); + if (!rc) { + /* keep PCIe reset asserted for 250ms */ + msleep(250); + pci_set_pcie_reset_state(pci_dev, pcie_deassert_reset); + /* Wait for 2s to reload flash and train the link */ + msleep(2000); + } + pci_restore_state(pci_dev); + pci_cfg_access_unlock(pci_dev); + return rc; +} + +/* + * genwqe_reload_bistream() - reload card bitstream + * + * Set the appropriate register and call fundamental reset to reaload the card + * bitstream. + * + * Return: 0 on success, error code otherwise + */ +static int genwqe_reload_bistream(struct genwqe_dev *cd) +{ + struct pci_dev *pci_dev = cd->pci_dev; + int rc; + + dev_info(&pci_dev->dev, + "[%s] resetting card for bitstream reload\n", + __func__); + + genwqe_stop(cd); + + /* + * Cause a CPLD reprogram with the 'next_bitstream' + * partition on PCIe hot or fundamental reset + */ + __genwqe_writeq(cd, IO_SLC_CFGREG_SOFTRESET, + (cd->softreset & 0xcull) | 0x70ull); + + rc = genwqe_pci_fundamental_reset(pci_dev); + if (rc) { + /* + * A fundamental reset failure can be caused + * by lack of support on the arch, so we just + * log the error and try to start the card + * again. + */ + dev_err(&pci_dev->dev, + "[%s] err: failed to reset card for bitstream reload\n", + __func__); + } + + rc = genwqe_start(cd); + if (rc) { + dev_err(&pci_dev->dev, + "[%s] err: cannot start card services! (err=%d)\n", + __func__, rc); + return rc; + } + dev_info(&pci_dev->dev, + "[%s] card reloaded\n", __func__); + return 0; +} + + /** * genwqe_health_thread() - Health checking thread * @@ -846,6 +929,13 @@ static int genwqe_health_thread(void *data) } } + if (cd->card_state == GENWQE_CARD_RELOAD_BITSTREAM) { + /* Userspace requested card bitstream reload */ + rc = genwqe_reload_bistream(cd); + if (rc) + goto fatal_error; + } + cd->last_gfir = gfir; cond_resched(); } diff --git a/drivers/misc/genwqe/card_sysfs.c b/drivers/misc/genwqe/card_sysfs.c index a72a99266c3c..7232e40a3ad9 100644 --- a/drivers/misc/genwqe/card_sysfs.c +++ b/drivers/misc/genwqe/card_sysfs.c @@ -223,6 +223,30 @@ static ssize_t next_bitstream_store(struct device *dev, } static DEVICE_ATTR_RW(next_bitstream); +static ssize_t reload_bitstream_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int reload; + struct genwqe_dev *cd = dev_get_drvdata(dev); + + if (kstrtoint(buf, 0, &reload) < 0) + return -EINVAL; + + if (reload == 0x1) { + if (cd->card_state == GENWQE_CARD_UNUSED || + cd->card_state == GENWQE_CARD_USED) + cd->card_state = GENWQE_CARD_RELOAD_BITSTREAM; + else + return -EIO; + } else { + return -EINVAL; + } + + return count; +} +static DEVICE_ATTR_WO(reload_bitstream); + /* * Create device_attribute structures / params: name, mode, show, store * additional flag if valid in VF @@ -239,6 +263,7 @@ static struct attribute *genwqe_attributes[] = { &dev_attr_status.attr, &dev_attr_freerunning_timer.attr, &dev_attr_queue_working_time.attr, + &dev_attr_reload_bitstream.attr, NULL, }; diff --git a/include/uapi/linux/genwqe/genwqe_card.h b/include/uapi/linux/genwqe/genwqe_card.h index 795e957bb840..4fc065f29255 100644 --- a/include/uapi/linux/genwqe/genwqe_card.h +++ b/include/uapi/linux/genwqe/genwqe_card.h @@ -328,6 +328,7 @@ enum genwqe_card_state { GENWQE_CARD_UNUSED = 0, GENWQE_CARD_USED = 1, GENWQE_CARD_FATAL_ERROR = 2, + GENWQE_CARD_RELOAD_BITSTREAM = 3, GENWQE_CARD_STATE_MAX, }; -- cgit v1.2.3 From fb145456fa4f4311f90703aeee058bab3b274bf8 Mon Sep 17 00:00:00 2001 From: Kleber Sacilotto de Souza Date: Wed, 4 Jun 2014 10:57:51 -0300 Subject: GenWQE: Add support for EEH error recovery This patch implements the callbacks and functions necessary to have EEH recovery support. It adds a config option to enable or disable explicit calls to trigger platform specific mechanisms on error recovery paths. This option is enabled by default only on PPC64 systems and can be overritten via debugfs. If this option is enabled, on the error recovery path the driver will call pci_channel_offline() to check for error condition and issue non-raw MMIO reads to trigger early EEH detection in case of hardware failures. This is necessary since the driver MMIO helper funtions use raw accessors. Signed-off-by: Kleber Sacilotto de Souza Acked-by: Frank Haverkamp Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/Kconfig | 6 +++ drivers/misc/genwqe/card_base.c | 79 +++++++++++++++++++++++++++++++------- drivers/misc/genwqe/card_base.h | 2 + drivers/misc/genwqe/card_ddcb.c | 24 +++++++++--- drivers/misc/genwqe/card_debugfs.c | 7 ++++ drivers/misc/genwqe/card_dev.c | 5 +++ drivers/misc/genwqe/card_utils.c | 10 +++++ 7 files changed, 115 insertions(+), 18 deletions(-) diff --git a/drivers/misc/genwqe/Kconfig b/drivers/misc/genwqe/Kconfig index 6069d8cd79d7..4c0a033cbfdb 100644 --- a/drivers/misc/genwqe/Kconfig +++ b/drivers/misc/genwqe/Kconfig @@ -11,3 +11,9 @@ menuconfig GENWQE Enables PCIe card driver for IBM GenWQE accelerators. The user-space interface is described in include/linux/genwqe/genwqe_card.h. + +config GENWQE_PLATFORM_ERROR_RECOVERY + int "Use platform recovery procedures (0=off, 1=on)" + depends on GENWQE + default 1 if PPC64 + default 0 diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c index e6cc3e1e7326..87ebaba9b133 100644 --- a/drivers/misc/genwqe/card_base.c +++ b/drivers/misc/genwqe/card_base.c @@ -140,6 +140,12 @@ static struct genwqe_dev *genwqe_dev_alloc(void) cd->class_genwqe = class_genwqe; cd->debugfs_genwqe = debugfs_genwqe; + /* + * This comes from kernel config option and can be overritten via + * debugfs. + */ + cd->use_platform_recovery = CONFIG_GENWQE_PLATFORM_ERROR_RECOVERY; + init_waitqueue_head(&cd->queue_waitq); spin_lock_init(&cd->file_lock); @@ -943,6 +949,19 @@ static int genwqe_health_thread(void *data) return 0; fatal_error: + if (cd->use_platform_recovery) { + /* + * Since we use raw accessors, EEH errors won't be detected + * by the platform until we do a non-raw MMIO or config space + * read + */ + readq(cd->mmio + IO_SLC_CFGREG_GFIR); + + /* We do nothing if the card is going over PCI recovery */ + if (pci_channel_offline(pci_dev)) + return -EIO; + } + dev_err(&pci_dev->dev, "[%s] card unusable. Please trigger unbind!\n", __func__); @@ -1048,6 +1067,9 @@ static int genwqe_pci_setup(struct genwqe_dev *cd) pci_set_master(pci_dev); pci_enable_pcie_error_reporting(pci_dev); + /* EEH recovery requires PCIe fundamental reset */ + pci_dev->needs_freset = 1; + /* request complete BAR-0 space (length = 0) */ cd->mmio_len = pci_resource_len(pci_dev, 0); cd->mmio = pci_iomap(pci_dev, 0, 0); @@ -1186,23 +1208,40 @@ static pci_ers_result_t genwqe_err_error_detected(struct pci_dev *pci_dev, dev_err(&pci_dev->dev, "[%s] state=%d\n", __func__, state); - if (pci_dev == NULL) - return PCI_ERS_RESULT_NEED_RESET; - cd = dev_get_drvdata(&pci_dev->dev); if (cd == NULL) - return PCI_ERS_RESULT_NEED_RESET; + return PCI_ERS_RESULT_DISCONNECT; - switch (state) { - case pci_channel_io_normal: - return PCI_ERS_RESULT_CAN_RECOVER; - case pci_channel_io_frozen: - return PCI_ERS_RESULT_NEED_RESET; - case pci_channel_io_perm_failure: + /* Stop the card */ + genwqe_health_check_stop(cd); + genwqe_stop(cd); + + /* + * On permanent failure, the PCI code will call device remove + * after the return of this function. + * genwqe_stop() can be called twice. + */ + if (state == pci_channel_io_perm_failure) { return PCI_ERS_RESULT_DISCONNECT; + } else { + genwqe_pci_remove(cd); + return PCI_ERS_RESULT_NEED_RESET; } +} + +static pci_ers_result_t genwqe_err_slot_reset(struct pci_dev *pci_dev) +{ + int rc; + struct genwqe_dev *cd = dev_get_drvdata(&pci_dev->dev); - return PCI_ERS_RESULT_NEED_RESET; + rc = genwqe_pci_setup(cd); + if (!rc) { + return PCI_ERS_RESULT_RECOVERED; + } else { + dev_err(&pci_dev->dev, + "err: problems with PCI setup (err=%d)\n", rc); + return PCI_ERS_RESULT_DISCONNECT; + } } static pci_ers_result_t genwqe_err_result_none(struct pci_dev *dev) @@ -1210,8 +1249,22 @@ static pci_ers_result_t genwqe_err_result_none(struct pci_dev *dev) return PCI_ERS_RESULT_NONE; } -static void genwqe_err_resume(struct pci_dev *dev) +static void genwqe_err_resume(struct pci_dev *pci_dev) { + int rc; + struct genwqe_dev *cd = dev_get_drvdata(&pci_dev->dev); + + rc = genwqe_start(cd); + if (!rc) { + rc = genwqe_health_check_start(cd); + if (rc) + dev_err(&pci_dev->dev, + "err: cannot start health checking! (err=%d)\n", + rc); + } else { + dev_err(&pci_dev->dev, + "err: cannot start card services! (err=%d)\n", rc); + } } static int genwqe_sriov_configure(struct pci_dev *dev, int numvfs) @@ -1234,7 +1287,7 @@ static struct pci_error_handlers genwqe_err_handler = { .error_detected = genwqe_err_error_detected, .mmio_enabled = genwqe_err_result_none, .link_reset = genwqe_err_result_none, - .slot_reset = genwqe_err_result_none, + .slot_reset = genwqe_err_slot_reset, .resume = genwqe_err_resume, }; diff --git a/drivers/misc/genwqe/card_base.h b/drivers/misc/genwqe/card_base.h index 0e608a288603..67abd8cb2247 100644 --- a/drivers/misc/genwqe/card_base.h +++ b/drivers/misc/genwqe/card_base.h @@ -291,6 +291,8 @@ struct genwqe_dev { struct task_struct *health_thread; wait_queue_head_t health_waitq; + int use_platform_recovery; /* use platform recovery mechanisms */ + /* char device */ dev_t devnum_genwqe; /* major/minor num card */ struct class *class_genwqe; /* reference to class object */ diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c index c8046db2d5a2..f0de6153bea2 100644 --- a/drivers/misc/genwqe/card_ddcb.c +++ b/drivers/misc/genwqe/card_ddcb.c @@ -1118,7 +1118,21 @@ static irqreturn_t genwqe_pf_isr(int irq, void *dev_id) * safer, but slower for the good-case ... See above. */ gfir = __genwqe_readq(cd, IO_SLC_CFGREG_GFIR); - if ((gfir & GFIR_ERR_TRIGGER) != 0x0) { + if (((gfir & GFIR_ERR_TRIGGER) != 0x0) && + !pci_channel_offline(pci_dev)) { + + if (cd->use_platform_recovery) { + /* + * Since we use raw accessors, EEH errors won't be + * detected by the platform until we do a non-raw + * MMIO or config space read + */ + readq(cd->mmio + IO_SLC_CFGREG_GFIR); + + /* Don't do anything if the PCI channel is frozen */ + if (pci_channel_offline(pci_dev)) + goto exit; + } wake_up_interruptible(&cd->health_waitq); @@ -1126,12 +1140,12 @@ static irqreturn_t genwqe_pf_isr(int irq, void *dev_id) * By default GFIRs causes recovery actions. This * count is just for debug when recovery is masked. */ - printk_ratelimited(KERN_ERR - "%s %s: [%s] GFIR=%016llx\n", - GENWQE_DEVNAME, dev_name(&pci_dev->dev), - __func__, gfir); + dev_err_ratelimited(&pci_dev->dev, + "[%s] GFIR=%016llx\n", + __func__, gfir); } + exit: return IRQ_HANDLED; } diff --git a/drivers/misc/genwqe/card_debugfs.c b/drivers/misc/genwqe/card_debugfs.c index 0a33ade64109..c9b4d6d0eb99 100644 --- a/drivers/misc/genwqe/card_debugfs.c +++ b/drivers/misc/genwqe/card_debugfs.c @@ -485,6 +485,13 @@ int genwqe_init_debugfs(struct genwqe_dev *cd) goto err1; } + file = debugfs_create_u32("use_platform_recovery", 0666, root, + &cd->use_platform_recovery); + if (!file) { + ret = -ENOMEM; + goto err1; + } + cd->debugfs_root = root; return 0; err1: diff --git a/drivers/misc/genwqe/card_dev.c b/drivers/misc/genwqe/card_dev.c index 1d2f163a1906..aae42555e2ca 100644 --- a/drivers/misc/genwqe/card_dev.c +++ b/drivers/misc/genwqe/card_dev.c @@ -1048,10 +1048,15 @@ static long genwqe_ioctl(struct file *filp, unsigned int cmd, int rc = 0; struct genwqe_file *cfile = (struct genwqe_file *)filp->private_data; struct genwqe_dev *cd = cfile->cd; + struct pci_dev *pci_dev = cd->pci_dev; struct genwqe_reg_io __user *io; u64 val; u32 reg_offs; + /* Return -EIO if card hit EEH */ + if (pci_channel_offline(pci_dev)) + return -EIO; + if (_IOC_TYPE(cmd) != GENWQE_IOC_CODE) return -EINVAL; diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index 62cc6bb3f62e..4a500582eef0 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -53,12 +53,17 @@ */ int __genwqe_writeq(struct genwqe_dev *cd, u64 byte_offs, u64 val) { + struct pci_dev *pci_dev = cd->pci_dev; + if (cd->err_inject & GENWQE_INJECT_HARDWARE_FAILURE) return -EIO; if (cd->mmio == NULL) return -EIO; + if (pci_channel_offline(pci_dev)) + return -EIO; + __raw_writeq((__force u64)cpu_to_be64(val), cd->mmio + byte_offs); return 0; } @@ -99,12 +104,17 @@ u64 __genwqe_readq(struct genwqe_dev *cd, u64 byte_offs) */ int __genwqe_writel(struct genwqe_dev *cd, u64 byte_offs, u32 val) { + struct pci_dev *pci_dev = cd->pci_dev; + if (cd->err_inject & GENWQE_INJECT_HARDWARE_FAILURE) return -EIO; if (cd->mmio == NULL) return -EIO; + if (pci_channel_offline(pci_dev)) + return -EIO; + __raw_writel((__force u32)cpu_to_be32(val), cd->mmio + byte_offs); return 0; } -- cgit v1.2.3 From 93b772b25fa905c9158ee73c11c87b48668eabd0 Mon Sep 17 00:00:00 2001 From: Kleber Sacilotto de Souza Date: Wed, 4 Jun 2014 10:57:52 -0300 Subject: GenWQE: Improve hardware error recovery Currently, in the event of a fatal hardware error, the driver tries a recovery procedure that calls pci_reset_function() to reset the card. This is not sufficient in some cases, needing a fundamental reset to bring the card back. This patch implements a call to the platform fundamental reset procedure on the error recovery path if GENWQE_PLATFORM_ERROR_RECOVERY is enabled. This is implemented by default only on PPC64, since this can cause problems on other archs, e.g. zSeries, where the platform has its own recovery procedures, leading to a potencial race conditition. For these cases, the recovery is kept as it was before. Signed-off-by: Kleber Sacilotto de Souza Acked-by: Frank Haverkamp Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_base.c | 45 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c index 87ebaba9b133..abb796187ceb 100644 --- a/drivers/misc/genwqe/card_base.c +++ b/drivers/misc/genwqe/card_base.c @@ -797,6 +797,41 @@ static int genwqe_pci_fundamental_reset(struct pci_dev *pci_dev) return rc; } + +static int genwqe_platform_recovery(struct genwqe_dev *cd) +{ + struct pci_dev *pci_dev = cd->pci_dev; + int rc; + + dev_info(&pci_dev->dev, + "[%s] resetting card for error recovery\n", __func__); + + /* Clear out error injection flags */ + cd->err_inject &= ~(GENWQE_INJECT_HARDWARE_FAILURE | + GENWQE_INJECT_GFIR_FATAL | + GENWQE_INJECT_GFIR_INFO); + + genwqe_stop(cd); + + /* Try recoverying the card with fundamental reset */ + rc = genwqe_pci_fundamental_reset(pci_dev); + if (!rc) { + rc = genwqe_start(cd); + if (!rc) + dev_info(&pci_dev->dev, + "[%s] card recovered\n", __func__); + else + dev_err(&pci_dev->dev, + "[%s] err: cannot start card services! (err=%d)\n", + __func__, rc); + } else { + dev_err(&pci_dev->dev, + "[%s] card reset failed\n", __func__); + } + + return rc; +} + /* * genwqe_reload_bistream() - reload card bitstream * @@ -875,6 +910,7 @@ static int genwqe_health_thread(void *data) struct pci_dev *pci_dev = cd->pci_dev; u64 gfir, gfir_masked, slu_unitcfg, app_unitcfg; + health_thread_begin: while (!kthread_should_stop()) { rc = wait_event_interruptible_timeout(cd->health_waitq, (genwqe_health_check_cond(cd, &gfir) || @@ -960,6 +996,15 @@ static int genwqe_health_thread(void *data) /* We do nothing if the card is going over PCI recovery */ if (pci_channel_offline(pci_dev)) return -EIO; + + /* + * If it's supported by the platform, we try a fundamental reset + * to recover from a fatal error. Otherwise, we continue to wait + * for an external recovery procedure to take care of it. + */ + rc = genwqe_platform_recovery(cd); + if (!rc) + goto health_thread_begin; } dev_err(&pci_dev->dev, -- cgit v1.2.3 From d584f69d3217fb4336e58f14afca07d709b1c205 Mon Sep 17 00:00:00 2001 From: Kleber Sacilotto de Souza Date: Wed, 4 Jun 2014 10:57:53 -0300 Subject: GenWQE: Increase driver version number Increase genwqe driver version number. Signed-off-by: Kleber Sacilotto de Souza Acked-by: Frank Haverkamp Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/genwqe_driver.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/misc/genwqe/genwqe_driver.h b/drivers/misc/genwqe/genwqe_driver.h index cd5263163a6e..a506e9aa2d57 100644 --- a/drivers/misc/genwqe/genwqe_driver.h +++ b/drivers/misc/genwqe/genwqe_driver.h @@ -36,7 +36,7 @@ #include #include -#define DRV_VERS_STRING "2.0.15" +#define DRV_VERS_STRING "2.0.21" /* * Static minor number assignement, until we decide/implement -- cgit v1.2.3 From 7276883f1f98cd0a92fdc049f69bdc0912f7fc16 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Wed, 9 Jul 2014 12:46:39 +0200 Subject: misc/GenWQE: fix pci_enable_msi usage GenWQE used to call pci_enable_msi_block to allocate a desired number of MSI's. If that was not possible pci_enable_msi_block returned with a smaller number which might be possible to allocate. GenWQE then called pci_enable_msi_block with that number. Since commit a30d0108b "GenWQE: Use pci_enable_msi_exact() instead of pci_enable_msi_block()" pci_enable_msi_exact is used which fails if the desired number of MSI's was not possible to allocate. Change GenWQE to use pci_enable_msi_range to restore the old behavior. Signed-off-by: Sebastian Ott Reviewed-by: Alexander Gordeev Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_ddcb.c | 4 +--- drivers/misc/genwqe/card_utils.c | 10 ++++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/misc/genwqe/card_ddcb.c b/drivers/misc/genwqe/card_ddcb.c index f0de6153bea2..dc9851a5540e 100644 --- a/drivers/misc/genwqe/card_ddcb.c +++ b/drivers/misc/genwqe/card_ddcb.c @@ -1251,9 +1251,7 @@ int genwqe_setup_service_layer(struct genwqe_dev *cd) } rc = genwqe_set_interrupt_capability(cd, GENWQE_MSI_IRQS); - if (rc > 0) - rc = genwqe_set_interrupt_capability(cd, rc); - if (rc != 0) { + if (rc) { rc = -ENODEV; goto stop_kthread; } diff --git a/drivers/misc/genwqe/card_utils.c b/drivers/misc/genwqe/card_utils.c index 4a500582eef0..a6400f09229c 100644 --- a/drivers/misc/genwqe/card_utils.c +++ b/drivers/misc/genwqe/card_utils.c @@ -728,10 +728,12 @@ int genwqe_set_interrupt_capability(struct genwqe_dev *cd, int count) int rc; struct pci_dev *pci_dev = cd->pci_dev; - rc = pci_enable_msi_exact(pci_dev, count); - if (rc == 0) - cd->flags |= GENWQE_FLAG_MSI_ENABLED; - return rc; + rc = pci_enable_msi_range(pci_dev, 1, count); + if (rc < 0) + return rc; + + cd->flags |= GENWQE_FLAG_MSI_ENABLED; + return 0; } /** -- cgit v1.2.3 From f3d8e8788b4efbd13b6e888e85af51385d87d306 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 23 Jun 2014 15:10:35 +0300 Subject: mei: move from misc to char device We need to support more then one mei interface hence the simple misc devices is not longer an option In order not break the user space a device with pci function 0 need to be linked to /dev/mei Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/main.c | 148 ++++++++++++++++++++++++++++++++++++--------- drivers/misc/mei/mei_dev.h | 9 ++- drivers/misc/mei/pci-me.c | 3 +- drivers/misc/mei/pci-txe.c | 2 +- 4 files changed, 131 insertions(+), 31 deletions(-) diff --git a/drivers/misc/mei/main.c b/drivers/misc/mei/main.c index 66f0a1a06451..401a3d526cd0 100644 --- a/drivers/misc/mei/main.c +++ b/drivers/misc/mei/main.c @@ -32,7 +32,6 @@ #include #include #include -#include #include @@ -49,19 +48,12 @@ */ static int mei_open(struct inode *inode, struct file *file) { - struct miscdevice *misc = file->private_data; - struct pci_dev *pdev; - struct mei_cl *cl; struct mei_device *dev; + struct mei_cl *cl; int err; - if (!misc->parent) - return -ENODEV; - - pdev = container_of(misc->parent, struct pci_dev, dev); - - dev = pci_get_drvdata(pdev); + dev = container_of(inode->i_cdev, struct mei_device, cdev); if (!dev) return -ENODEV; @@ -667,46 +659,148 @@ static const struct file_operations mei_fops = { .llseek = no_llseek }; -/* - * Misc Device Struct +static struct class *mei_class; +static dev_t mei_devt; +#define MEI_MAX_DEVS MINORMASK +static DEFINE_MUTEX(mei_minor_lock); +static DEFINE_IDR(mei_idr); + +/** + * mei_minor_get - obtain next free device minor number + * + * @dev: device pointer + * + * returns allocated minor, or -ENOSPC if no free minor left */ -static struct miscdevice mei_misc_device = { - .name = "mei", - .fops = &mei_fops, - .minor = MISC_DYNAMIC_MINOR, -}; +static int mei_minor_get(struct mei_device *dev) +{ + int ret; + + mutex_lock(&mei_minor_lock); + ret = idr_alloc(&mei_idr, dev, 0, MEI_MAX_DEVS, GFP_KERNEL); + if (ret >= 0) + dev->minor = ret; + else if (ret == -ENOSPC) + dev_err(&dev->pdev->dev, "too many mei devices\n"); + mutex_unlock(&mei_minor_lock); + return ret; +} -int mei_register(struct mei_device *dev) +/** + * mei_minor_free - mark device minor number as free + * + * @dev: device pointer + */ +static void mei_minor_free(struct mei_device *dev) { - int ret; - mei_misc_device.parent = &dev->pdev->dev; - ret = misc_register(&mei_misc_device); - if (ret) + mutex_lock(&mei_minor_lock); + idr_remove(&mei_idr, dev->minor); + mutex_unlock(&mei_minor_lock); +} + +int mei_register(struct mei_device *dev, struct device *parent) +{ + struct device *clsdev; /* class device */ + int ret, devno; + + ret = mei_minor_get(dev); + if (ret < 0) return ret; - if (mei_dbgfs_register(dev, mei_misc_device.name)) - dev_err(&dev->pdev->dev, "cannot register debugfs\n"); + /* Fill in the data structures */ + devno = MKDEV(MAJOR(mei_devt), dev->minor); + cdev_init(&dev->cdev, &mei_fops); + dev->cdev.owner = mei_fops.owner; + + /* Add the device */ + ret = cdev_add(&dev->cdev, devno, 1); + if (ret) { + dev_err(parent, "unable to add device %d:%d\n", + MAJOR(mei_devt), dev->minor); + goto err_dev_add; + } + + clsdev = device_create(mei_class, parent, devno, + NULL, "mei%d", dev->minor); + + if (IS_ERR(clsdev)) { + dev_err(parent, "unable to create device %d:%d\n", + MAJOR(mei_devt), dev->minor); + ret = PTR_ERR(clsdev); + goto err_dev_create; + } + + ret = mei_dbgfs_register(dev, dev_name(clsdev)); + if (ret) { + dev_err(clsdev, "cannot register debugfs ret = %d\n", ret); + goto err_dev_dbgfs; + } return 0; + +err_dev_dbgfs: + device_destroy(mei_class, devno); +err_dev_create: + cdev_del(&dev->cdev); +err_dev_add: + mei_minor_free(dev); + return ret; } EXPORT_SYMBOL_GPL(mei_register); void mei_deregister(struct mei_device *dev) { + int devno; + + devno = dev->cdev.dev; + cdev_del(&dev->cdev); + mei_dbgfs_deregister(dev); - misc_deregister(&mei_misc_device); - mei_misc_device.parent = NULL; + + device_destroy(mei_class, devno); + + mei_minor_free(dev); } EXPORT_SYMBOL_GPL(mei_deregister); static int __init mei_init(void) { - return mei_cl_bus_init(); + int ret; + + mei_class = class_create(THIS_MODULE, "mei"); + if (IS_ERR(mei_class)) { + pr_err("couldn't create class\n"); + ret = PTR_ERR(mei_class); + goto err; + } + + ret = alloc_chrdev_region(&mei_devt, 0, MEI_MAX_DEVS, "mei"); + if (ret < 0) { + pr_err("unable to allocate char dev region\n"); + goto err_class; + } + + ret = mei_cl_bus_init(); + if (ret < 0) { + pr_err("unable to initialize bus\n"); + goto err_chrdev; + } + + return 0; + +err_chrdev: + unregister_chrdev_region(mei_devt, MEI_MAX_DEVS); +err_class: + class_destroy(mei_class); +err: + return ret; } static void __exit mei_exit(void) { + unregister_chrdev_region(mei_devt, MEI_MAX_DEVS); + class_destroy(mei_class); mei_cl_bus_exit(); } diff --git a/drivers/misc/mei/mei_dev.h b/drivers/misc/mei/mei_dev.h index 5c7e990e2f22..2afa3d69107d 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -400,6 +400,10 @@ struct mei_cfg { /** * struct mei_device - MEI private device struct + * @pdev - pointer to pci device struct + * @cdev - character device + * @minor - minor number allocated for device + * * @reset_count - limits the number of consecutive resets * @hbm_state - state of host bus message protocol * @pg_event - power gating event @@ -412,6 +416,9 @@ struct mei_cfg { */ struct mei_device { struct pci_dev *pdev; /* pointer to pci device struct */ + struct cdev cdev; + int minor; + /* * lists of queues */ @@ -741,7 +748,7 @@ static inline int mei_dbgfs_register(struct mei_device *dev, const char *name) static inline void mei_dbgfs_deregister(struct mei_device *dev) {} #endif /* CONFIG_DEBUG_FS */ -int mei_register(struct mei_device *dev); +int mei_register(struct mei_device *dev, struct device *parent); void mei_deregister(struct mei_device *dev); #define MEI_HDR_FMT "hdr:host=%02d me=%02d len=%d internal=%1d comp=%1d" diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 1b46c64a649f..887ace14efa6 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -31,7 +31,6 @@ #include #include #include -#include #include @@ -207,7 +206,7 @@ static int mei_me_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_ME_RPM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); - err = mei_register(dev); + err = mei_register(dev, &pdev->dev); if (err) goto release_irq; diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index 2343c6236df9..d7480fe8994d 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -149,7 +149,7 @@ static int mei_txe_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pm_runtime_set_autosuspend_delay(&pdev->dev, MEI_TXI_RPM_TIMEOUT); pm_runtime_use_autosuspend(&pdev->dev); - err = mei_register(dev); + err = mei_register(dev, &pdev->dev); if (err) goto release_irq; -- cgit v1.2.3 From 602214db45188819b19917c3259ed21a203d2c83 Mon Sep 17 00:00:00 2001 From: Tomas Winkler Date: Mon, 23 Jun 2014 15:10:36 +0300 Subject: mei: sysfs: add Documentation mei class attributes Add sysfs attributes Documentation entries for /sys/class/mei Signed-off-by: Tomas Winkler Signed-off-by: Alexander Usyskin Signed-off-by: Greg Kroah-Hartman --- Documentation/ABI/testing/sysfs-class-mei | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-mei diff --git a/Documentation/ABI/testing/sysfs-class-mei b/Documentation/ABI/testing/sysfs-class-mei new file mode 100644 index 000000000000..0ec8b8178c41 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-mei @@ -0,0 +1,16 @@ +What: /sys/class/mei/ +Date: May 2014 +KernelVersion: 3.17 +Contact: Tomas Winkler +Description: + The mei/ class sub-directory belongs to mei device class + + +What: /sys/class/mei/meiN/ +Date: May 2014 +KernelVersion: 3.17 +Contact: Tomas Winkler +Description: + The /sys/class/mei/meiN directory is created for + each probed mei device + -- cgit v1.2.3 From d238a0ec8b850e81185f1b176c1f436684ce476b Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Mon, 23 Jun 2014 15:10:37 +0300 Subject: mei: add WPT second mei interface Add WPT second mei interface. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me-regs.h | 1 + drivers/misc/mei/pci-me.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h index a7856c0ac576..c5feafdd58a8 100644 --- a/drivers/misc/mei/hw-me-regs.h +++ b/drivers/misc/mei/hw-me-regs.h @@ -115,6 +115,7 @@ #define MEI_DEV_ID_LPT_HR 0x8CBA /* Lynx Point H Refresh */ #define MEI_DEV_ID_WPT_LP 0x9CBA /* Wildcat Point LP */ +#define MEI_DEV_ID_WPT_LP_2 0x9CBB /* Wildcat Point LP 2 */ /* Host Firmware Status Registers in PCI Config Space */ #define PCI_CFG_HFS_1 0x40 diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index 887ace14efa6..f0da3c91f0a8 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -81,6 +81,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { {MEI_PCI_DEVICE(MEI_DEV_ID_LPT_LP, 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)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_WPT_LP_2, mei_me_pch_cfg)}, /* required last entry */ {0, } -- cgit v1.2.3 From fc2f677465861b3ab0331d172859dbd668eed9f2 Mon Sep 17 00:00:00 2001 From: Fabian Frederick Date: Sun, 15 Jun 2014 08:22:42 +0200 Subject: drivers/misc/carma/carma-fpga.c: use PTR_ERR_OR_ZERO replace IS_ERR/PTR_ERR Cc: Grant Likely Cc: Rob Herring Cc: Greg Kroah-Hartman Signed-off-by: Fabian Frederick Signed-off-by: Greg Kroah-Hartman --- drivers/misc/carma/carma-fpga.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/misc/carma/carma-fpga.c b/drivers/misc/carma/carma-fpga.c index 14d90eae605b..55e913b7eb11 100644 --- a/drivers/misc/carma/carma-fpga.c +++ b/drivers/misc/carma/carma-fpga.c @@ -954,10 +954,7 @@ static int data_debugfs_init(struct fpga_device *priv) { priv->dbg_entry = debugfs_create_file(drv_name, S_IRUGO, NULL, priv, &data_debug_fops); - if (IS_ERR(priv->dbg_entry)) - return PTR_ERR(priv->dbg_entry); - - return 0; + return PTR_ERR_OR_ZERO(priv->dbg_entry); } static void data_debugfs_exit(struct fpga_device *priv) -- cgit v1.2.3 From da86920f4a193c8d55cfcb006e2e74b4bfab21d0 Mon Sep 17 00:00:00 2001 From: Terry Chia Date: Wed, 2 Jul 2014 21:02:25 +0800 Subject: Add MODULE_DESCRIPTION to dummy-irq.c and lkdtm.c in drivers/misc This starts to address https://bugzilla.kernel.org/show_bug.cgi?id=10770 Signed-off-by: Terry Chia Signed-off-by: Greg Kroah-Hartman --- drivers/misc/dummy-irq.c | 1 + drivers/misc/lkdtm.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/misc/dummy-irq.c b/drivers/misc/dummy-irq.c index 4d0db15df115..acbbe0390be4 100644 --- a/drivers/misc/dummy-irq.c +++ b/drivers/misc/dummy-irq.c @@ -61,3 +61,4 @@ MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jiri Kosina"); module_param(irq, uint, 0444); MODULE_PARM_DESC(irq, "The IRQ to register for"); +MODULE_DESCRIPTION("Dummy IRQ handler driver"); diff --git a/drivers/misc/lkdtm.c b/drivers/misc/lkdtm.c index d66a2f24f6b3..b5abe34120b8 100644 --- a/drivers/misc/lkdtm.c +++ b/drivers/misc/lkdtm.c @@ -870,3 +870,4 @@ module_init(lkdtm_module_init); module_exit(lkdtm_module_exit); MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Kprobe module for testing crash dumps"); -- cgit v1.2.3 From e013ac312c79379b26cf29012cfbb37c68f79283 Mon Sep 17 00:00:00 2001 From: Yue Zhang Date: Fri, 27 Jun 2014 18:19:48 -0700 Subject: Tools: hv: fix file overwriting of hv_fcopy_daemon hv_fcopy_daemon fails to overwrite a file if the target file already exits. Add O_TRUNC flag on opening. Signed-off-by: Yue Zhang Signed-off-by: Greg Kroah-Hartman --- tools/hv/hv_fcopy_daemon.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/hv/hv_fcopy_daemon.c b/tools/hv/hv_fcopy_daemon.c index fba1c75aa484..8f96b3ee0724 100644 --- a/tools/hv/hv_fcopy_daemon.c +++ b/tools/hv/hv_fcopy_daemon.c @@ -88,7 +88,8 @@ static int hv_start_fcopy(struct hv_start_fcopy *smsg) } } - target_fd = open(target_fname, O_RDWR | O_CREAT | O_CLOEXEC, 0744); + target_fd = open(target_fname, + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); if (target_fd == -1) { syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); goto done; -- cgit v1.2.3 From 7a446d635dd752a5326bbc458707364a288b3663 Mon Sep 17 00:00:00 2001 From: Jason Wang Date: Mon, 30 Jun 2014 14:14:48 +0800 Subject: hyperv: remove meaningless pr_err() in vmbus_recvpacket_raw() All its callers depends on the return value of -ENOBUFS to reallocate a bigger buffer and retry the receiving. So there's no need to call pr_err() here since it was not a real issue, otherwise syslog will be flooded by this false warning. Cc: Haiyang Zhang Signed-off-by: Jason Wang Signed-off-by: K. Y. Srinivasan Signed-off-by: Greg Kroah-Hartman --- drivers/hv/channel.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c index 284cf66489f4..531a593912ec 100644 --- a/drivers/hv/channel.c +++ b/drivers/hv/channel.c @@ -808,12 +808,8 @@ int vmbus_recvpacket_raw(struct vmbus_channel *channel, void *buffer, *buffer_actual_len = packetlen; - if (packetlen > bufferlen) { - pr_err("Buffer too small - needed %d bytes but " - "got space for only %d bytes\n", - packetlen, bufferlen); + if (packetlen > bufferlen) return -ENOBUFS; - } *requestid = desc.trans_id; -- cgit v1.2.3 From d8477126f46b036b26d95b127689a3774a080e34 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 5 Jun 2014 23:05:49 +0200 Subject: pcmcia: sa1100: H3100 and H3600 share a driver When building a iPAQ H3100-only kernel with PCMCIA enabled, we get this build error: ERROR: "pcmcia_h3600_init" [drivers/pcmcia/sa1100_cs.ko] undefined! The defconfig normally works fine because it enables both H3100 and H3600 support. This patch fixes the Makefile to build the driver if at least one of the two machines are selected. Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/pcmcia/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile index 7745b512a87c..fd55a6951402 100644 --- a/drivers/pcmcia/Makefile +++ b/drivers/pcmcia/Makefile @@ -49,6 +49,7 @@ sa1100_cs-y += sa1100_generic.o sa1100_cs-$(CONFIG_SA1100_ASSABET) += sa1100_assabet.o sa1100_cs-$(CONFIG_SA1100_CERF) += sa1100_cerf.o sa1100_cs-$(CONFIG_SA1100_COLLIE) += pxa2xx_sharpsl.o +sa1100_cs-$(CONFIG_SA1100_H3100) += sa1100_h3600.o sa1100_cs-$(CONFIG_SA1100_H3600) += sa1100_h3600.o sa1100_cs-$(CONFIG_SA1100_NANOENGINE) += sa1100_nanoengine.o sa1100_cs-$(CONFIG_SA1100_SHANNON) += sa1100_shannon.o -- cgit v1.2.3 From f1674f213ec81c55c909bb6805d3502b5883754d Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 5 Jun 2014 23:05:48 +0200 Subject: pcmcia: pxa2xx: fix logic for lubbock The lubbock platform uses the sa1111 companion chip with a pxa250 CPU, which means it requires both the PCMCIA_SA1111 and the PCMCIA_PXA2XX code to be built into the kernel. Unfortunately, the Makefile and Kconfig don't agree on how this is accomplished, leading to a situation where you get this link error when building a lubbock kernel with PCMCIA_SA1111 enabled but PCMCIA_PXA2XX disabled: ERROR: "pxa2xx_configure_sockets" [drivers/pcmcia/sa1111_cs.ko] undefined! ERROR: "pxa2xx_drv_pcmcia_ops" [drivers/pcmcia/sa1111_cs.ko] undefined! ERROR: "pxa2xx_drv_pcmcia_add_one" [drivers/pcmcia/sa1111_cs.ko] undefined! This patch changes the Kconfig code to disallow that particular configuration. Signed-off-by: Arnd Bergmann Signed-off-by: Greg Kroah-Hartman --- drivers/pcmcia/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/pcmcia/Kconfig b/drivers/pcmcia/Kconfig index 0c657d6af03d..51cf8083b299 100644 --- a/drivers/pcmcia/Kconfig +++ b/drivers/pcmcia/Kconfig @@ -202,6 +202,7 @@ config PCMCIA_SA1111 depends on ARM && SA1111 && PCMCIA select PCMCIA_SOC_COMMON select PCMCIA_SA11XX_BASE if ARCH_SA1100 + select PCMCIA_PXA2XX if ARCH_LUBBOCK && SA1111 help Say Y here to include support for SA1111-based PCMCIA or CF sockets, found on the Jornada 720, Graphicsmaster and other @@ -217,7 +218,6 @@ config PCMCIA_PXA2XX || ARCOM_PCMCIA || ARCH_PXA_ESERIES || MACH_STARGATE2 \ || MACH_VPAC270 || MACH_BALLOON3 || MACH_COLIBRI \ || MACH_COLIBRI320 || MACH_H4700) - select PCMCIA_SA1111 if ARCH_LUBBOCK && SA1111 select PCMCIA_SOC_COMMON help Say Y here to include support for the PXA2xx PCMCIA controller -- cgit v1.2.3 From 58409f9d21a9d372e35857b5b8aaf334997b127b Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Thu, 5 Jun 2014 23:05:47 +0200 Subject: pcmcia: journada720: use sa1100 pin interfaces correctly commit dabd14684bc2 "PCMCIA: sa1111: remove duplicated initializers" incorrectly moved some code into the pcmcia_jornada720_init, causing a few build errors, and for unknown reasons, the driver lacks an inclusion of , so we get the build errors, and more: sa1111_jornada720.c: In function 'pcmcia_jornada720_init': sa1111_jornada720.c:101:3: error: implicit declaration of function 'IOMEM' [-Werror=implicit-function-declaration] GRER |= 0x00000002; ^ sa1111_jornada720.c:104:3: warning: passing argument 1 of 'sa1111_set_io_dir' from incompatible pointer type [enabled by default] sa1111_set_io_dir(dev, pin, 0, 0); ^ This patch uses the SA1111_DEV() to convert the dev pointer to the correct type before passing it and adds the missing include. Signed-off-by: Arnd Bergmann Cc: Russell King Cc: Kristoffer Ericson Cc: linux-pcmcia@lists.infradead.org Signed-off-by: Greg Kroah-Hartman --- drivers/pcmcia/sa1111_jornada720.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/pcmcia/sa1111_jornada720.c b/drivers/pcmcia/sa1111_jornada720.c index 3baa3ef09682..40e040314503 100644 --- a/drivers/pcmcia/sa1111_jornada720.c +++ b/drivers/pcmcia/sa1111_jornada720.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -94,6 +95,7 @@ static struct pcmcia_low_level jornada720_pcmcia_ops = { int pcmcia_jornada720_init(struct device *dev) { int ret = -ENODEV; + struct sa1111_dev *sadev = SA1111_DEV(dev); if (machine_is_jornada720()) { unsigned int pin = GPIO_A0 | GPIO_A1 | GPIO_A2 | GPIO_A3; @@ -101,12 +103,12 @@ int pcmcia_jornada720_init(struct device *dev) GRER |= 0x00000002; /* Set GPIO_A<3:1> to be outputs for PCMCIA/CF power controller: */ - sa1111_set_io_dir(dev, pin, 0, 0); - sa1111_set_io(dev, pin, 0); - sa1111_set_sleep_io(dev, pin, 0); + sa1111_set_io_dir(sadev, pin, 0, 0); + sa1111_set_io(sadev, pin, 0); + sa1111_set_sleep_io(sadev, pin, 0); sa11xx_drv_pcmcia_ops(&jornada720_pcmcia_ops); - ret = sa1111_pcmcia_add(dev, &jornada720_pcmcia_ops, + ret = sa1111_pcmcia_add(sadev, &jornada720_pcmcia_ops, sa11xx_drv_pcmcia_add_one); } -- cgit v1.2.3 From 3915fc87dee83947f219f9347d47674f48c8c022 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 9 Jun 2014 18:12:12 +0300 Subject: VME: remove duplicate CA91CX42_DCTL_VDW_M define The CA91CX42_DCTL_VDW_M define is cut and pasted twice so we can delete the second instance. Signed-off-by: Dan Carpenter Signed-off-by: Greg Kroah-Hartman --- drivers/vme/bridges/vme_ca91cx42.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/vme/bridges/vme_ca91cx42.h b/drivers/vme/bridges/vme_ca91cx42.h index 02a7c794db05..d46b12dc3b82 100644 --- a/drivers/vme/bridges/vme_ca91cx42.h +++ b/drivers/vme/bridges/vme_ca91cx42.h @@ -360,7 +360,6 @@ static const int CA91CX42_VSI_TO[] = { VSI0_TO, VSI1_TO, VSI2_TO, VSI3_TO, */ #define CA91CX42_DCTL_L2V (1<<31) #define CA91CX42_DCTL_VDW_M (3<<22) -#define CA91CX42_DCTL_VDW_M (3<<22) #define CA91CX42_DCTL_VDW_D8 0 #define CA91CX42_DCTL_VDW_D16 (1<<22) #define CA91CX42_DCTL_VDW_D32 (1<<23) -- cgit v1.2.3 From b12ce5f24da12fbd4d9e903e5f8f5236eae8707a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 21 Jun 2014 08:08:09 -0700 Subject: i8k: Drop all labels Labels are known to be wrong for several Dell laptops. For example, a single fan may be shown as right fan when in reality it sits on the left side of the chassis. Drop all labels to avoid such inaccuracies. Users can select labels in the sensors configuration file instead if desired. Signed-off-by: Guenter Roeck Cc: Andreas Mohr Signed-off-by: Greg Kroah-Hartman --- drivers/char/i8k.c | 47 +++++++++++++---------------------------------- 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c index d915707d2ba1..3dc1a724ecc2 100644 --- a/drivers/char/i8k.c +++ b/drivers/char/i8k.c @@ -542,20 +542,6 @@ static ssize_t i8k_hwmon_set_pwm(struct device *dev, return err < 0 ? -EIO : count; } -static ssize_t i8k_hwmon_show_label(struct device *dev, - struct device_attribute *devattr, - char *buf) -{ - static const char *labels[3] = { - "CPU", - "Left Fan", - "Right Fan", - }; - int index = to_sensor_dev_attr(devattr)->index; - - return sprintf(buf, "%s\n", labels[index]); -} - static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0); static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1); static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2); @@ -568,41 +554,34 @@ static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL, I8K_FAN_RIGHT); static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, i8k_hwmon_set_pwm, I8K_FAN_RIGHT); -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 0); -static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 1); -static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_label, NULL, 2); static struct attribute *i8k_attrs[] = { &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ - &sensor_dev_attr_temp1_label.dev_attr.attr, /* 1 */ - &sensor_dev_attr_temp2_input.dev_attr.attr, /* 2 */ - &sensor_dev_attr_temp3_input.dev_attr.attr, /* 3 */ - &sensor_dev_attr_temp4_input.dev_attr.attr, /* 4 */ - &sensor_dev_attr_fan1_input.dev_attr.attr, /* 5 */ - &sensor_dev_attr_pwm1.dev_attr.attr, /* 6 */ - &sensor_dev_attr_fan1_label.dev_attr.attr, /* 7 */ - &sensor_dev_attr_fan2_input.dev_attr.attr, /* 8 */ - &sensor_dev_attr_pwm2.dev_attr.attr, /* 9 */ - &sensor_dev_attr_fan2_label.dev_attr.attr, /* 10 */ + &sensor_dev_attr_temp2_input.dev_attr.attr, /* 1 */ + &sensor_dev_attr_temp3_input.dev_attr.attr, /* 2 */ + &sensor_dev_attr_temp4_input.dev_attr.attr, /* 3 */ + &sensor_dev_attr_fan1_input.dev_attr.attr, /* 4 */ + &sensor_dev_attr_pwm1.dev_attr.attr, /* 5 */ + &sensor_dev_attr_fan2_input.dev_attr.attr, /* 6 */ + &sensor_dev_attr_pwm2.dev_attr.attr, /* 7 */ NULL }; static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, int index) { - if ((index == 0 || index == 1) && - !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) + if (index == 0 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) return 0; - if (index == 2 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) + if (index == 1 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) return 0; - if (index == 3 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) + if (index == 2 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) return 0; - if (index == 4 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) + if (index == 3 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) return 0; - if (index >= 5 && index <= 7 && + if (index >= 4 && index <= 5 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) return 0; - if (index >= 8 && index <= 10 && + if (index >= 6 && index <= 7 && !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) return 0; -- cgit v1.2.3 From 81474fc2fae9397ade2577398e5d03ab2b24af95 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 21 Jun 2014 08:08:10 -0700 Subject: i8k: Add support for configurable maximum fan speed value Newer Dell systems provide more granular fan speed selection. Add support for it. Signed-off-by: Guenter Roeck Cc: Andreas Mohr Signed-off-by: Greg Kroah-Hartman --- drivers/char/i8k.c | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c index 3dc1a724ecc2..6a6c085efa69 100644 --- a/drivers/char/i8k.c +++ b/drivers/char/i8k.c @@ -65,6 +65,8 @@ static char bios_version[4]; static struct device *i8k_hwmon_dev; static u32 i8k_hwmon_flags; static int i8k_fan_mult; +static int i8k_pwm_mult; +static int i8k_fan_max = I8K_FAN_HIGH; #define I8K_HWMON_HAVE_TEMP1 (1 << 0) #define I8K_HWMON_HAVE_TEMP2 (1 << 1) @@ -97,6 +99,10 @@ static int fan_mult = I8K_FAN_MULT; module_param(fan_mult, int, 0); MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with"); +static int fan_max = I8K_FAN_HIGH; +module_param(fan_max, int, 0); +MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed"); + static int i8k_open_fs(struct inode *inode, struct file *file); static long i8k_ioctl(struct file *, unsigned int, unsigned long); @@ -274,7 +280,7 @@ static int i8k_set_fan(int fan, int speed) { struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; - speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed); + speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); regs.ebx = (fan & 0xff) | (speed << 8); return i8k_smm(®s) ? : i8k_get_fan_status(fan); @@ -519,7 +525,7 @@ static ssize_t i8k_hwmon_show_pwm(struct device *dev, status = i8k_get_fan_status(index); if (status < 0) return -EIO; - return sprintf(buf, "%d\n", clamp_val(status * 128, 0, 255)); + return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255)); } static ssize_t i8k_hwmon_set_pwm(struct device *dev, @@ -533,7 +539,7 @@ static ssize_t i8k_hwmon_set_pwm(struct device *dev, err = kstrtoul(buf, 10, &val); if (err) return err; - val = clamp_val(DIV_ROUND_CLOSEST(val, 128), 0, 2); + val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max); mutex_lock(&i8k_mutex); err = i8k_set_fan(index, val); @@ -636,6 +642,27 @@ static int __init i8k_init_hwmon(void) return 0; } +struct i8k_config_data { + int fan_mult; + int fan_max; +}; + +enum i8k_configs { + DELL_STUDIO, + DELL_XPS_M140, +}; + +static const struct i8k_config_data i8k_config_data[] = { + [DELL_STUDIO] = { + .fan_mult = 1, + .fan_max = I8K_FAN_HIGH, + }, + [DELL_XPS_M140] = { + .fan_mult = 1, + .fan_max = I8K_FAN_HIGH, + }, +}; + static struct dmi_system_id i8k_dmi_table[] __initdata = { { .ident = "Dell Inspiron", @@ -706,7 +733,7 @@ static struct dmi_system_id i8k_dmi_table[] __initdata = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), }, - .driver_data = (void *)1, /* fan multiplier override */ + .driver_data = (void *)&i8k_config_data[DELL_STUDIO], }, { .ident = "Dell XPS M140", @@ -714,7 +741,7 @@ static struct dmi_system_id i8k_dmi_table[] __initdata = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), }, - .driver_data = (void *)1, /* fan multiplier override */ + .driver_data = (void *)&i8k_config_data[DELL_XPS_M140], }, { } }; @@ -754,9 +781,17 @@ static int __init i8k_probe(void) } i8k_fan_mult = fan_mult; + i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ id = dmi_first_match(i8k_dmi_table); - if (id && fan_mult == I8K_FAN_MULT && id->driver_data) - i8k_fan_mult = (unsigned long)id->driver_data; + if (id && id->driver_data) { + const struct i8k_config_data *conf = id->driver_data; + + if (fan_mult == I8K_FAN_MULT && conf->fan_mult) + i8k_fan_mult = conf->fan_mult; + if (fan_max == I8K_FAN_HIGH && conf->fan_max) + i8k_fan_max = conf->fan_max; + } + i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max); return 0; } -- cgit v1.2.3 From d5d222605503dd39513b3baeb9475ddf316511d7 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 21 Jun 2014 08:08:11 -0700 Subject: i8k: uapi: Introduce define for new highest fan speed Some Dell laptops support fan speeds of {0, 1, 2, 3} instead of {0, 1, 2}. Add a define for it. Signed-off-by: Guenter Roeck Cc: Andreas Mohr Signed-off-by: Greg Kroah-Hartman --- include/uapi/linux/i8k.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/uapi/linux/i8k.h b/include/uapi/linux/i8k.h index 1c45ba505115..133d02f03c25 100644 --- a/include/uapi/linux/i8k.h +++ b/include/uapi/linux/i8k.h @@ -34,7 +34,8 @@ #define I8K_FAN_OFF 0 #define I8K_FAN_LOW 1 #define I8K_FAN_HIGH 2 -#define I8K_FAN_MAX I8K_FAN_HIGH +#define I8K_FAN_TURBO 3 +#define I8K_FAN_MAX I8K_FAN_TURBO #define I8K_VOL_UP 1 #define I8K_VOL_DOWN 2 -- cgit v1.2.3 From 7b88344631536cdc258b83c7a67f9117dd05e208 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 21 Jun 2014 08:08:12 -0700 Subject: i8k: Add support for Dell Precision 490 and Latitude D520 Both systems need non-standard parameters for fan multiplier and maximum fan speed. Signed-off-by: Guenter Roeck Cc: Andreas Mohr Signed-off-by: Greg Kroah-Hartman --- drivers/char/i8k.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c index 6a6c085efa69..4e10c4b3c8e3 100644 --- a/drivers/char/i8k.c +++ b/drivers/char/i8k.c @@ -648,11 +648,21 @@ struct i8k_config_data { }; enum i8k_configs { + DELL_LATITUDE_D520, + DELL_PRECISION_490, DELL_STUDIO, DELL_XPS_M140, }; static const struct i8k_config_data i8k_config_data[] = { + [DELL_LATITUDE_D520] = { + .fan_mult = 1, + .fan_max = I8K_FAN_TURBO, + }, + [DELL_PRECISION_490] = { + .fan_mult = 1, + .fan_max = I8K_FAN_TURBO, + }, [DELL_STUDIO] = { .fan_mult = 1, .fan_max = I8K_FAN_HIGH, @@ -685,6 +695,14 @@ static struct dmi_system_id i8k_dmi_table[] __initdata = { DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), }, }, + { + .ident = "Dell Latitude D520", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), + }, + .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], + }, { .ident = "Dell Latitude 2", .matches = { @@ -706,6 +724,15 @@ static struct dmi_system_id i8k_dmi_table[] __initdata = { DMI_MATCH(DMI_PRODUCT_NAME, "MP061"), }, }, + { + .ident = "Dell Precision 490", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, + "Precision WorkStation 490"), + }, + .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], + }, { .ident = "Dell Precision", .matches = { -- cgit v1.2.3 From 4171ee9e3cd27718ce21f19aa00ea892655b28af Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 10 Jun 2014 10:51:08 -0700 Subject: bsr: avoid format string leaking into device name This makes sure a format string cannot accidentally leak into a device name. Signed-off-by: Kees Cook Signed-off-by: Greg Kroah-Hartman --- drivers/char/bsr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/bsr.c b/drivers/char/bsr.c index 8fedbc250414..a6cef548e01e 100644 --- a/drivers/char/bsr.c +++ b/drivers/char/bsr.c @@ -259,7 +259,7 @@ static int bsr_add_node(struct device_node *bn) } cur->bsr_device = device_create(bsr_class, NULL, cur->bsr_dev, - cur, cur->bsr_name); + cur, "%s", cur->bsr_name); if (IS_ERR(cur->bsr_device)) { printk(KERN_ERR "device_create failed for %s\n", cur->bsr_name); -- cgit v1.2.3 From 8a26af30ff55bec82443e718e26efd6b3a8bba83 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 11 Jun 2014 09:04:33 +0300 Subject: char: xilinx_hwicap: missing error code if ioremap() fails Return -ENOMEM instead of success if ioremap() fails. Signed-off-by: Dan Carpenter Reviewed-by: Jingoo Han Signed-off-by: Greg Kroah-Hartman --- drivers/char/xilinx_hwicap/xilinx_hwicap.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/char/xilinx_hwicap/xilinx_hwicap.c b/drivers/char/xilinx_hwicap/xilinx_hwicap.c index f6345f932e46..9b1a5ac4881d 100644 --- a/drivers/char/xilinx_hwicap/xilinx_hwicap.c +++ b/drivers/char/xilinx_hwicap/xilinx_hwicap.c @@ -661,6 +661,7 @@ static int hwicap_setup(struct device *dev, int id, drvdata->base_address = ioremap(drvdata->mem_start, drvdata->mem_size); if (!drvdata->base_address) { dev_err(dev, "ioremap() failed\n"); + retval = -ENOMEM; goto failed2; } -- cgit v1.2.3 From b86e1926be37c77deeb176466d98678feab04066 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 11 Jun 2014 13:17:41 +0300 Subject: mfd: vexpress: fix error handling vexpress_syscfg_regmap_init() This function should be returning an ERR_PTR() on failure instead of NULL. Also there is a use after free bug if regmap_init() fails because we free "func" and then dereference doing the return. Signed-off-by: Dan Carpenter Acked-by: Pawel Moll Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vexpress-syscfg.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/misc/vexpress-syscfg.c b/drivers/misc/vexpress-syscfg.c index 73068e50e56d..3250fc1df0aa 100644 --- a/drivers/misc/vexpress-syscfg.c +++ b/drivers/misc/vexpress-syscfg.c @@ -199,7 +199,7 @@ static struct regmap *vexpress_syscfg_regmap_init(struct device *dev, func = kzalloc(sizeof(*func) + sizeof(*func->template) * num, GFP_KERNEL); if (!func) - return NULL; + return ERR_PTR(-ENOMEM); func->syscfg = syscfg; func->num_templates = num; @@ -231,10 +231,14 @@ static struct regmap *vexpress_syscfg_regmap_init(struct device *dev, func->regmap = regmap_init(dev, NULL, func, &vexpress_syscfg_regmap_config); - if (IS_ERR(func->regmap)) + if (IS_ERR(func->regmap)) { + void *err = func->regmap; + kfree(func); - else - list_add(&func->list, &syscfg->funcs); + return err; + } + + list_add(&func->list, &syscfg->funcs); return func->regmap; } -- cgit v1.2.3 From 968d04e8de53789ccdb9f74413eb497f155d266b Mon Sep 17 00:00:00 2001 From: Federico Vaga Date: Thu, 26 Jun 2014 09:46:24 +0200 Subject: ipoctal: protect only the real critical section In some conditions (echo or particular sequence of special characters), on buffer push, the tty layer calls the write operation while we are holding the spinlock. This means deadlock within the same process on kernels version < 3.12. It seems not a problem on recent kernel, but the patch still valid as locking optimization. The protected variables by the spinlock are: xmit_buf, nb_bytes, pointer_read and pointer_write. So, this patch reduces the locked area in the IRQ handler only to these variables. Most of the code inside the locked area in the IRQ handler is not protected elsewhere; it means that it is not protected at all. Signed-off-by: Federico Vaga Acked-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 141094e7c06e..69687f156999 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -177,19 +177,20 @@ static void ipoctal_irq_tx(struct ipoctal_channel *channel) if (channel->nb_bytes == 0) return; + spin_lock(&channel->lock); value = channel->tty_port.xmit_buf[*pointer_write]; iowrite8(value, &channel->regs->w.thr); channel->stats.tx++; (*pointer_write)++; *pointer_write = *pointer_write % PAGE_SIZE; channel->nb_bytes--; + spin_unlock(&channel->lock); } static void ipoctal_irq_channel(struct ipoctal_channel *channel) { u8 isr, sr; - spin_lock(&channel->lock); /* The HW is organized in pair of channels. See which register we need * to read from */ isr = ioread8(&channel->block_regs->r.isr); @@ -213,8 +214,6 @@ static void ipoctal_irq_channel(struct ipoctal_channel *channel) /* TX of each character */ if ((isr & channel->isr_tx_rdy_mask) && (sr & SR_TX_READY)) ipoctal_irq_tx(channel); - - spin_unlock(&channel->lock); } static irqreturn_t ipoctal_irq_handler(void *arg) -- cgit v1.2.3 From 4847cc073adb91695980906ae557957460fda453 Mon Sep 17 00:00:00 2001 From: Federico Vaga Date: Thu, 3 Jul 2014 10:53:58 +0200 Subject: ipoctal: request_irq after configuration The request for an IRQ handler must be done after whole configuration. This was not the case for this driver which request the IRQ in the middle of the configuration. Sometimes, it happens that something is not completely configured, we recieve an interrupt thus we stumble into troubles in the IRQ handler. Signed-off-by: Federico Vaga Acked-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/devices/ipoctal.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/ipack/devices/ipoctal.c b/drivers/ipack/devices/ipoctal.c index 69687f156999..e41bef048c23 100644 --- a/drivers/ipack/devices/ipoctal.c +++ b/drivers/ipack/devices/ipoctal.c @@ -323,13 +323,6 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, &block_regs[i].w.imr); } - /* - * IP-OCTAL has different addresses to copy its IRQ vector. - * Depending of the carrier these addresses are accesible or not. - * More info in the datasheet. - */ - ipoctal->dev->bus->ops->request_irq(ipoctal->dev, - ipoctal_irq_handler, ipoctal); /* Dummy write */ iowrite8(1, ipoctal->mem8_space + 1); @@ -390,6 +383,14 @@ static int ipoctal_inst_slot(struct ipoctal *ipoctal, unsigned int bus_nr, dev_set_drvdata(tty_dev, channel); } + /* + * IP-OCTAL has different addresses to copy its IRQ vector. + * Depending of the carrier these addresses are accesible or not. + * More info in the datasheet. + */ + ipoctal->dev->bus->ops->request_irq(ipoctal->dev, + ipoctal_irq_handler, ipoctal); + return 0; } -- cgit v1.2.3 From 4719ebfd89f0a93bb3fd7f251ecd5d0acbc8bdf1 Mon Sep 17 00:00:00 2001 From: Andre Heider Date: Sun, 29 Jun 2014 18:21:35 +0200 Subject: uio: uio_pruss: use struct device Get rid of the repeating &dev->dev constructs and prevent introducing new ones. Signed-off-by: Andre Heider Signed-off-by: Greg Kroah-Hartman --- drivers/uio/uio_pruss.c | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c index 96c4a19b1918..c28d6e2e3df2 100644 --- a/drivers/uio/uio_pruss.c +++ b/drivers/uio/uio_pruss.c @@ -91,8 +91,7 @@ static irqreturn_t pruss_handler(int irq, struct uio_info *info) return IRQ_HANDLED; } -static void pruss_cleanup(struct platform_device *dev, - struct uio_pruss_dev *gdev) +static void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev) { int cnt; struct uio_info *p = gdev->info; @@ -103,7 +102,7 @@ static void pruss_cleanup(struct platform_device *dev, } iounmap(gdev->prussio_vaddr); if (gdev->ddr_vaddr) { - dma_free_coherent(&dev->dev, extram_pool_sz, gdev->ddr_vaddr, + dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr, gdev->ddr_paddr); } if (gdev->sram_vaddr) @@ -115,13 +114,14 @@ static void pruss_cleanup(struct platform_device *dev, kfree(gdev); } -static int pruss_probe(struct platform_device *dev) +static int pruss_probe(struct platform_device *pdev) { struct uio_info *p; struct uio_pruss_dev *gdev; struct resource *regs_prussio; + struct device *dev = &pdev->dev; int ret = -ENODEV, cnt = 0, len; - struct uio_pruss_pdata *pdata = dev_get_platdata(&dev->dev); + struct uio_pruss_pdata *pdata = dev_get_platdata(dev); gdev = kzalloc(sizeof(struct uio_pruss_dev), GFP_KERNEL); if (!gdev) @@ -132,10 +132,11 @@ static int pruss_probe(struct platform_device *dev) kfree(gdev); return -ENOMEM; } + /* Power on PRU in case its not done as part of boot-loader */ - gdev->pruss_clk = clk_get(&dev->dev, "pruss"); + gdev->pruss_clk = clk_get(dev, "pruss"); if (IS_ERR(gdev->pruss_clk)) { - dev_err(&dev->dev, "Failed to get clock\n"); + dev_err(dev, "Failed to get clock\n"); ret = PTR_ERR(gdev->pruss_clk); kfree(gdev->info); kfree(gdev); @@ -144,14 +145,14 @@ static int pruss_probe(struct platform_device *dev) clk_enable(gdev->pruss_clk); } - regs_prussio = platform_get_resource(dev, IORESOURCE_MEM, 0); + regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!regs_prussio) { - dev_err(&dev->dev, "No PRUSS I/O resource specified\n"); + dev_err(dev, "No PRUSS I/O resource specified\n"); goto out_free; } if (!regs_prussio->start) { - dev_err(&dev->dev, "Invalid memory resource\n"); + dev_err(dev, "Invalid memory resource\n"); goto out_free; } @@ -161,27 +162,27 @@ static int pruss_probe(struct platform_device *dev) (unsigned long)gen_pool_dma_alloc(gdev->sram_pool, sram_pool_sz, &gdev->sram_paddr); if (!gdev->sram_vaddr) { - dev_err(&dev->dev, "Could not allocate SRAM pool\n"); + dev_err(dev, "Could not allocate SRAM pool\n"); goto out_free; } } - gdev->ddr_vaddr = dma_alloc_coherent(&dev->dev, extram_pool_sz, + gdev->ddr_vaddr = dma_alloc_coherent(dev, extram_pool_sz, &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); if (!gdev->ddr_vaddr) { - dev_err(&dev->dev, "Could not allocate external memory\n"); + dev_err(dev, "Could not allocate external memory\n"); goto out_free; } len = resource_size(regs_prussio); gdev->prussio_vaddr = ioremap(regs_prussio->start, len); if (!gdev->prussio_vaddr) { - dev_err(&dev->dev, "Can't remap PRUSS I/O address range\n"); + dev_err(dev, "Can't remap PRUSS I/O address range\n"); goto out_free; } gdev->pintc_base = pdata->pintc_base; - gdev->hostirq_start = platform_get_irq(dev, 0); + gdev->hostirq_start = platform_get_irq(pdev, 0); for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) { p->mem[0].addr = regs_prussio->start; @@ -204,12 +205,12 @@ static int pruss_probe(struct platform_device *dev) p->handler = pruss_handler; p->priv = gdev; - ret = uio_register_device(&dev->dev, p); + ret = uio_register_device(dev, p); if (ret < 0) goto out_free; } - platform_set_drvdata(dev, gdev); + platform_set_drvdata(pdev, gdev); return 0; out_free: @@ -221,7 +222,7 @@ static int pruss_remove(struct platform_device *dev) { struct uio_pruss_dev *gdev = platform_get_drvdata(dev); - pruss_cleanup(dev, gdev); + pruss_cleanup(&dev->dev, gdev); return 0; } -- cgit v1.2.3 From 3768528af3270953696e10d65faedb960d33e9a4 Mon Sep 17 00:00:00 2001 From: Robin van der Gracht Date: Mon, 16 Jun 2014 16:38:56 +0200 Subject: ti-st: st-kim: Dont let probe fail when debugfs is disabled Signed-off-by: Robin van der Gracht Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_kim.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 9d3dbb28734b..45007be7cfb7 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -778,7 +778,7 @@ static int kim_probe(struct platform_device *pdev) pr_info("sysfs entries created\n"); kim_debugfs_dir = debugfs_create_dir("ti-st", NULL); - if (IS_ERR(kim_debugfs_dir)) { + if (!kim_debugfs_dir) { pr_err(" debugfs entries creation failed "); err = -EIO; goto err_debugfs_dir; @@ -788,7 +788,6 @@ static int kim_probe(struct platform_device *pdev) kim_gdata, &version_debugfs_fops); debugfs_create_file("protocols", S_IRUGO, kim_debugfs_dir, kim_gdata, &list_debugfs_fops); - pr_info(" debugfs entries created "); return 0; err_debugfs_dir: -- cgit v1.2.3 From 5b35b20d4eb4c7fa6263939304c69207f791535f Mon Sep 17 00:00:00 2001 From: Kleber Sacilotto de Souza Date: Thu, 10 Jul 2014 10:17:08 -0300 Subject: GenWQE: Remove unnecessary include The include for the UAPI header file from card_base.c can be removed since it's already included on card_base.h. Signed-off-by: Kleber Sacilotto de Souza Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_base.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c index abb796187ceb..dc8c04ae5113 100644 --- a/drivers/misc/genwqe/card_base.c +++ b/drivers/misc/genwqe/card_base.c @@ -38,7 +38,6 @@ #include #include #include -#include #include "card_base.h" #include "card_ddcb.h" -- cgit v1.2.3 From 6f8a1031eeaf2a7e2994cd39086793270aff0a44 Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Mon, 23 Jun 2014 16:51:27 +0530 Subject: spmi: Remove duplicate inclusion of module.h module.h was included twice. Signed-off-by: Sachin Kamat Signed-off-by: Greg Kroah-Hartman --- drivers/spmi/spmi.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/spmi/spmi.c b/drivers/spmi/spmi.c index 3b5780710d50..1d92f5103ebf 100644 --- a/drivers/spmi/spmi.c +++ b/drivers/spmi/spmi.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include -- cgit v1.2.3 From 1db121d6584166a9a8ef4a6c52e23151c764d5b5 Mon Sep 17 00:00:00 2001 From: Andreas Noever Date: Thu, 10 Jul 2014 18:58:04 +0200 Subject: MAINTAINERS: Add thunderbolt driver Signed-off-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 134483f206e4..3e3499513e8a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7791,6 +7791,11 @@ S: Maintained F: include/linux/mmc/dw_mmc.h F: drivers/mmc/host/dw_mmc* +THUNDERBOLT DRIVER +M: Andreas Noever +S: Maintained +F: drivers/thunderbolt/ + TIMEKEEPING, CLOCKSOURCE CORE, NTP M: John Stultz M: Thomas Gleixner -- cgit v1.2.3 From 4813d2e7361468f06b400dea6706786d8e55dba6 Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Wed, 25 Jun 2014 15:31:37 -0600 Subject: tools: memory-hotplug fix unexpected operator error on-off-test is a bash script and invoked from /bin/sh This results in the following error: ./on-off-test.sh: 9: [: !=: unexpected operator Changed Makefile to use bash instead. Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/memory-hotplug/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/memory-hotplug/Makefile b/tools/testing/selftests/memory-hotplug/Makefile index 350bfeda3aa8..058c76f5d102 100644 --- a/tools/testing/selftests/memory-hotplug/Makefile +++ b/tools/testing/selftests/memory-hotplug/Makefile @@ -1,6 +1,6 @@ all: run_tests: - @/bin/sh ./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]" + @/bin/bash ./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]" clean: -- cgit v1.2.3 From 5e4ff6950352ab2f4b6f18c66c235bfa95c39a2a Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Wed, 25 Jun 2014 11:43:44 -0600 Subject: tools: cpu-hotplug fix unexpected operator error on-off-test is a bash script and invoked from /bin/sh This results in the following error: ./on-off-test.sh: 9: [: !=: unexpected operator Changed Makefile to use bash instead. Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/cpu-hotplug/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/cpu-hotplug/Makefile b/tools/testing/selftests/cpu-hotplug/Makefile index ae5faf9aade2..790c23a9db44 100644 --- a/tools/testing/selftests/cpu-hotplug/Makefile +++ b/tools/testing/selftests/cpu-hotplug/Makefile @@ -1,6 +1,6 @@ all: run_tests: - @/bin/sh ./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]" + @/bin/bash ./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]" clean: -- cgit v1.2.3 From f15fed3da83212da8fa939ec70e38033ea35f8aa Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Wed, 25 Jun 2014 10:05:17 -0600 Subject: tools: fix mq_perf_tests compile warnings Fix numerous compile warnings in mq_perf_tests.c. All of these are wrong format in printfs when printing nvsec. Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mqueue/mq_perf_tests.c | 40 ++++++++++++++------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tools/testing/selftests/mqueue/mq_perf_tests.c b/tools/testing/selftests/mqueue/mq_perf_tests.c index 2fadd4b97045..94dae65eea41 100644 --- a/tools/testing/selftests/mqueue/mq_perf_tests.c +++ b/tools/testing/selftests/mqueue/mq_perf_tests.c @@ -296,9 +296,9 @@ static inline void open_queue(struct mq_attr *attr) printf("\n\tQueue %s created:\n", queue_path); printf("\t\tmq_flags:\t\t\t%s\n", result.mq_flags & O_NONBLOCK ? "O_NONBLOCK" : "(null)"); - printf("\t\tmq_maxmsg:\t\t\t%d\n", result.mq_maxmsg); - printf("\t\tmq_msgsize:\t\t\t%d\n", result.mq_msgsize); - printf("\t\tmq_curmsgs:\t\t\t%d\n", result.mq_curmsgs); + printf("\t\tmq_maxmsg:\t\t\t%lu\n", result.mq_maxmsg); + printf("\t\tmq_msgsize:\t\t\t%lu\n", result.mq_msgsize); + printf("\t\tmq_curmsgs:\t\t\t%lu\n", result.mq_curmsgs); } void *fake_cont_thread(void *arg) @@ -440,7 +440,7 @@ void *perf_test_thread(void *arg) shutdown(2, "clock_getres()", __LINE__); printf("\t\tMax priorities:\t\t\t%d\n", mq_prio_max); - printf("\t\tClock resolution:\t\t%d nsec%s\n", res.tv_nsec, + printf("\t\tClock resolution:\t\t%lu nsec%s\n", res.tv_nsec, res.tv_nsec > 1 ? "s" : ""); @@ -454,20 +454,20 @@ void *perf_test_thread(void *arg) recv_total.tv_nsec = 0; for (i = 0; i < TEST1_LOOPS; i++) do_send_recv(); - printf("\t\tSend msg:\t\t\t%d.%ds total time\n", + printf("\t\tSend msg:\t\t\t%ld.%lus total time\n", send_total.tv_sec, send_total.tv_nsec); nsec = ((unsigned long long)send_total.tv_sec * 1000000000 + send_total.tv_nsec) / TEST1_LOOPS; - printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); - printf("\t\tRecv msg:\t\t\t%d.%ds total time\n", + printf("\t\t\t\t\t\t%lld nsec/msg\n", nsec); + printf("\t\tRecv msg:\t\t\t%ld.%lus total time\n", recv_total.tv_sec, recv_total.tv_nsec); nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 + recv_total.tv_nsec) / TEST1_LOOPS; - printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); + printf("\t\t\t\t\t\t%lld nsec/msg\n", nsec); for (cur_test = test2; cur_test->desc != NULL; cur_test++) { - printf(cur_test->desc); + printf("%s:\n", cur_test->desc); printf("\t\t(%d iterations)\n", TEST2_LOOPS); prio_out = 0; send_total.tv_sec = 0; @@ -493,16 +493,16 @@ void *perf_test_thread(void *arg) cur_test->func(&prio_out); } printf("done.\n"); - printf("\t\tSend msg:\t\t\t%d.%ds total time\n", + printf("\t\tSend msg:\t\t\t%ld.%lus total time\n", send_total.tv_sec, send_total.tv_nsec); nsec = ((unsigned long long)send_total.tv_sec * 1000000000 + send_total.tv_nsec) / TEST2_LOOPS; - printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); - printf("\t\tRecv msg:\t\t\t%d.%ds total time\n", + printf("\t\t\t\t\t\t%lld nsec/msg\n", nsec); + printf("\t\tRecv msg:\t\t\t%ld.%lus total time\n", recv_total.tv_sec, recv_total.tv_nsec); nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 + recv_total.tv_nsec) / TEST2_LOOPS; - printf("\t\t\t\t\t\t%d nsec/msg\n", nsec); + printf("\t\t\t\t\t\t%lld nsec/msg\n", nsec); printf("\t\tDraining queue..."); fflush(stdout); clock_gettime(clock, &start); @@ -653,8 +653,10 @@ int main(int argc, char *argv[]) /* Tell the user our initial state */ printf("\nInitial system state:\n"); printf("\tUsing queue path:\t\t\t%s\n", queue_path); - printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", saved_limits.rlim_cur); - printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", saved_limits.rlim_max); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%ld\n", + (long) saved_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%ld\n", + (long) saved_limits.rlim_max); printf("\tMaximum Message Size:\t\t\t%d\n", saved_max_msgsize); printf("\tMaximum Queue Size:\t\t\t%d\n", saved_max_msgs); printf("\tNice value:\t\t\t\t%d\n", cur_nice); @@ -667,10 +669,10 @@ int main(int argc, char *argv[]) printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t(unlimited)\n"); printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t(unlimited)\n"); } else { - printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", - cur_limits.rlim_cur); - printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", - cur_limits.rlim_max); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%ld\n", + (long) cur_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%ld\n", + (long) cur_limits.rlim_max); } printf("\tMaximum Message Size:\t\t\t%d\n", cur_max_msgsize); printf("\tMaximum Queue Size:\t\t\t%d\n", cur_max_msgs); -- cgit v1.2.3 From ef9feb682dea988f731a2e5a2e8e498aba6a9da7 Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Wed, 25 Jun 2014 10:03:33 -0600 Subject: tools: fix mq_open_tests compile warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix several compile warnings - these are repeats like the ones below: gcc -O2 -lrt mq_open_tests.c -o mq_open_tests mq_open_tests.c: In function ‘main’: mq_open_tests.c:295:2: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘rlim_t’ [-Wformat=] printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", saved_limits.rlim_cur); ^ mq_open_tests.c: In function ‘shutdown’: mq_open_tests.c:83:9: warning: ignoring return value of ‘seteuid’, declared with attribute warn_unused_result [-Wunused-result] seteuid(0); Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mqueue/mq_open_tests.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/mqueue/mq_open_tests.c b/tools/testing/selftests/mqueue/mq_open_tests.c index 711cc2923047..9c1a5d359055 100644 --- a/tools/testing/selftests/mqueue/mq_open_tests.c +++ b/tools/testing/selftests/mqueue/mq_open_tests.c @@ -80,7 +80,8 @@ void shutdown(int exit_val, char *err_cause, int line_no) if (in_shutdown++) return; - seteuid(0); + if (seteuid(0) == -1) + perror("seteuid() failed"); if (queue != -1) if (mq_close(queue)) @@ -292,8 +293,10 @@ int main(int argc, char *argv[]) /* Tell the user our initial state */ printf("\nInitial system state:\n"); printf("\tUsing queue path:\t\t%s\n", queue_path); - printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", saved_limits.rlim_cur); - printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", saved_limits.rlim_max); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t%ld\n", + (long) saved_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t%ld\n", + (long) saved_limits.rlim_max); printf("\tMaximum Message Size:\t\t%d\n", saved_max_msgsize); printf("\tMaximum Queue Size:\t\t%d\n", saved_max_msgs); if (default_settings) { @@ -308,8 +311,8 @@ int main(int argc, char *argv[]) validate_current_settings(); printf("Adjusted system state for testing:\n"); - printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", cur_limits.rlim_cur); - printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", cur_limits.rlim_max); + printf("\tRLIMIT_MSGQUEUE(soft):\t\t%ld\n", (long) cur_limits.rlim_cur); + printf("\tRLIMIT_MSGQUEUE(hard):\t\t%ld\n", (long) cur_limits.rlim_max); printf("\tMaximum Message Size:\t\t%d\n", cur_max_msgsize); printf("\tMaximum Queue Size:\t\t%d\n", cur_max_msgs); if (default_settings) { @@ -454,7 +457,12 @@ int main(int argc, char *argv[]) else printf("Queue open with total size > 2GB when euid = 0 " "failed:\t\t\tPASS\n"); - seteuid(99); + + if (seteuid(99) == -1) { + perror("seteuid() failed"); + exit(1); + } + attr.mq_maxmsg = cur_max_msgs; attr.mq_msgsize = cur_max_msgsize; if (test_queue_fail(&attr, &result)) -- cgit v1.2.3 From b80f557042d4742b8c0ed69467d6b87a83186c8d Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Tue, 24 Jun 2014 19:53:36 -0600 Subject: tools: Fix mqueue Makefile compile linking order Makefile compile linking order is incorrect causing the compile to fail not finding librt symbols. /tmp/cceTqwFh.o: In function `test_queue_fail': mq_open_tests.c:(.text+0x6b): undefined reference to `mq_open' mq_open_tests.c:(.text+0x80): undefined reference to `mq_getattr' mq_open_tests.c:(.text+0xa2): undefined reference to `mq_close' mq_open_tests.c:(.text+0xcf): undefined reference to `mq_unlink' /tmp/cceTqwFh.o: In function `test_queue.constprop.6': mq_open_tests.c:(.text+0x15a): undefined reference to `mq_open' mq_open_tests.c:(.text+0x16f): undefined reference to `mq_getattr' mq_open_tests.c:(.text+0x195): undefined reference to `mq_close' mq_open_tests.c:(.text+0x1c2): undefined reference to `mq_unlink' /tmp/cceTqwFh.o: In function `shutdown.part.0': mq_open_tests.c:(.text.unlikely+0x5b): undefined reference to `mq_close' mq_open_tests.c:(.text.unlikely+0x7a): undefined reference to `mq_unlink' collect2: error: ld returned 1 exit status make: *** [all] Error 1 Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/mqueue/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/testing/selftests/mqueue/Makefile b/tools/testing/selftests/mqueue/Makefile index 218a122c7951..8056e2e68fa4 100644 --- a/tools/testing/selftests/mqueue/Makefile +++ b/tools/testing/selftests/mqueue/Makefile @@ -1,6 +1,6 @@ all: - gcc -O2 -lrt mq_open_tests.c -o mq_open_tests - gcc -O2 -lrt -lpthread -lpopt -o mq_perf_tests mq_perf_tests.c + gcc -O2 mq_open_tests.c -o mq_open_tests -lrt + gcc -O2 -o mq_perf_tests mq_perf_tests.c -lrt -lpthread -lpopt run_tests: @./mq_open_tests /test1 || echo "mq_open_tests: [FAIL]" -- cgit v1.2.3 From 6e7e6c348492cb7e0a8a36a9d74d098de6f93208 Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Tue, 24 Jun 2014 18:11:26 -0600 Subject: tools: fix kcmp_test compile warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kcmp_test.c: In function ‘main’: kcmp_test.c:85:5: warning: format ‘%li’ expects argument of type ‘long int’, but argument 2 has type ‘int’ [-Wformat=] ret, strerror(errno)); ^ Signed-off-by: Shuah Khan Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/kcmp/kcmp_test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/kcmp/kcmp_test.c b/tools/testing/selftests/kcmp/kcmp_test.c index fa4f1b37e045..dbba4084869c 100644 --- a/tools/testing/selftests/kcmp/kcmp_test.c +++ b/tools/testing/selftests/kcmp/kcmp_test.c @@ -81,7 +81,7 @@ int main(int argc, char **argv) /* Compare with self */ ret = sys_kcmp(pid1, pid1, KCMP_VM, 0, 0); if (ret) { - printf("FAIL: 0 expected but %li returned (%s)\n", + printf("FAIL: 0 expected but %d returned (%s)\n", ret, strerror(errno)); ret = -1; } else -- cgit v1.2.3 From ddddda9bc41c9214731def8665ad92414356d685 Mon Sep 17 00:00:00 2001 From: Shuah Khan Date: Wed, 2 Jul 2014 09:51:38 -0600 Subject: tools: selftests - create a separate hotplug target for full range test On some systems, hot-plug tests could hang forever waiting for cpu and memory to be ready to be offlined. A special hot-plug target is created to run full range of hot-plug tests. In default mode, hot-plug tests run in safe mode with a limited scope. In limited mode, cpu-hotplug test is run on a single cpu as opposed to all hotplug capable cpus, and memory hotplug test is run on 2% of hotplug capable memory instead of 10%. In addition to the above change, cpu-hotplug is chnged to change processor affinity to cpu 0 so it doesn't impact itself while the test runs. Signed-off-by: Shuah Khan Cc: Kees Cook Cc: Michael Ellerman Cc: Benjamin Herrenschmidt Cc: Frederic Weisbecker Signed-off-by: Andrew Morton Signed-off-by: Greg Kroah-Hartman --- tools/testing/selftests/Makefile | 18 ++++++++ tools/testing/selftests/README.txt | 27 +++++++++-- tools/testing/selftests/cpu-hotplug/Makefile | 3 ++ tools/testing/selftests/cpu-hotplug/on-off-test.sh | 52 +++++++++++++++++++++- tools/testing/selftests/memory-hotplug/Makefile | 3 ++ .../selftests/memory-hotplug/on-off-test.sh | 8 ++++ 6 files changed, 105 insertions(+), 6 deletions(-) diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index e66e710cc595..4c2aa357e12f 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -12,6 +12,9 @@ TARGETS += powerpc TARGETS += user TARGETS += sysctl +TARGETS_HOTPLUG = cpu-hotplug +TARGETS_HOTPLUG += memory-hotplug + all: for TARGET in $(TARGETS); do \ make -C $$TARGET; \ @@ -22,6 +25,21 @@ run_tests: all make -C $$TARGET run_tests; \ done; +hotplug: + for TARGET in $(TARGETS_HOTPLUG); do \ + make -C $$TARGET; \ + done; + +run_hotplug: hotplug + for TARGET in $(TARGETS_HOTPLUG); do \ + make -C $$TARGET run_full_test; \ + done; + +clean_hotplug: + for TARGET in $(TARGETS_HOTPLUG); do \ + make -C $$TARGET clean; \ + done; + clean: for TARGET in $(TARGETS); do \ make -C $$TARGET clean; \ diff --git a/tools/testing/selftests/README.txt b/tools/testing/selftests/README.txt index 5e2faf9c55d3..2660d5ff9179 100644 --- a/tools/testing/selftests/README.txt +++ b/tools/testing/selftests/README.txt @@ -4,8 +4,15 @@ The kernel contains a set of "self tests" under the tools/testing/selftests/ directory. These are intended to be small unit tests to exercise individual code paths in the kernel. -Running the selftests -===================== +On some systems, hot-plug tests could hang forever waiting for cpu and +memory to be ready to be offlined. A special hot-plug target is created +to run full range of hot-plug tests. In default mode, hot-plug tests run +in safe mode with a limited scope. In limited mode, cpu-hotplug test is +run on a single cpu as opposed to all hotplug capable cpus, and memory +hotplug test is run on 2% of hotplug capable memory instead of 10%. + +Running the selftests (hotplug tests are run in limited mode) +============================================================= To build the tests: @@ -18,14 +25,26 @@ To run the tests: - note that some tests will require root privileges. - -To run only tests targetted for a single subsystem: +To run only tests targeted for a single subsystem: (including +hotplug targets in limited mode) $ make -C tools/testing/selftests TARGETS=cpu-hotplug run_tests See the top-level tools/testing/selftests/Makefile for the list of all possible targets. +Running the full range hotplug selftests +======================================== + +To build the tests: + + $ make -C tools/testing/selftests hotplug + +To run the tests: + + $ make -C tools/testing/selftests run_hotplug + +- note that some tests will require root privileges. Contributing new tests ====================== diff --git a/tools/testing/selftests/cpu-hotplug/Makefile b/tools/testing/selftests/cpu-hotplug/Makefile index 790c23a9db44..e9c28d8dc84b 100644 --- a/tools/testing/selftests/cpu-hotplug/Makefile +++ b/tools/testing/selftests/cpu-hotplug/Makefile @@ -3,4 +3,7 @@ all: run_tests: @/bin/bash ./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]" +run_full_test: + @/bin/bash ./on-off-test.sh -a || echo "cpu-hotplug selftests: [FAIL]" + clean: diff --git a/tools/testing/selftests/cpu-hotplug/on-off-test.sh b/tools/testing/selftests/cpu-hotplug/on-off-test.sh index bdde7cf428bb..98b1d6565f2c 100644 --- a/tools/testing/selftests/cpu-hotplug/on-off-test.sh +++ b/tools/testing/selftests/cpu-hotplug/on-off-test.sh @@ -11,6 +11,8 @@ prerequisite() exit 0 fi + taskset -p 01 $$ + SYSFS=`mount -t sysfs | head -1 | awk '{ print $3 }'` if [ ! -d "$SYSFS" ]; then @@ -22,6 +24,19 @@ prerequisite() echo $msg cpu hotplug is not supported >&2 exit 0 fi + + echo "CPU online/offline summary:" + online_cpus=`cat $SYSFS/devices/system/cpu/online` + online_max=${online_cpus##*-} + echo -e "\t Cpus in online state: $online_cpus" + + offline_cpus=`cat $SYSFS/devices/system/cpu/offline` + if [[ "a$offline_cpus" = "a" ]]; then + offline_cpus=0 + else + offline_max=${offline_cpus##*-} + fi + echo -e "\t Cpus in offline state: $offline_cpus" } # @@ -113,15 +128,25 @@ offline_cpu_expect_fail() } error=-12 +allcpus=0 priority=0 +online_cpus=0 +online_max=0 +offline_cpus=0 +offline_max=0 -while getopts e:hp: opt; do +while getopts e:ahp: opt; do case $opt in e) error=$OPTARG ;; + a) + allcpus=1 + ;; h) - echo "Usage $0 [ -e errno ] [ -p notifier-priority ]" + echo "Usage $0 [ -a ] [ -e errno ] [ -p notifier-priority ]" + echo -e "\t default offline one cpu" + echo -e "\t run with -a option to offline all cpus" exit ;; p) @@ -137,6 +162,29 @@ fi prerequisite +# +# Safe test (default) - offline and online one cpu +# +if [ $allcpus -eq 0 ]; then + echo "Limited scope test: one hotplug cpu" + echo -e "\t (leaves cpu in the original state):" + echo -e "\t online to offline to online: cpu $online_max" + offline_cpu_expect_success $online_max + online_cpu_expect_success $online_max + + if [[ $offline_cpus -gt 0 ]]; then + echo -e "\t offline to online to offline: cpu $offline_max" + online_cpu_expect_success $offline_max + offline_cpu_expect_success $offline_max + fi + exit 0 +else + echo "Full scope test: all hotplug cpus" + echo -e "\t online all offline cpus" + echo -e "\t offline all online cpus" + echo -e "\t online all offline cpus" +fi + # # Online all hot-pluggable CPUs # diff --git a/tools/testing/selftests/memory-hotplug/Makefile b/tools/testing/selftests/memory-hotplug/Makefile index 058c76f5d102..d46b8d489cd2 100644 --- a/tools/testing/selftests/memory-hotplug/Makefile +++ b/tools/testing/selftests/memory-hotplug/Makefile @@ -1,6 +1,9 @@ all: run_tests: + @/bin/bash ./on-off-test.sh -r 2 || echo "memory-hotplug selftests: [FAIL]" + +run_full_test: @/bin/bash ./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]" clean: diff --git a/tools/testing/selftests/memory-hotplug/on-off-test.sh b/tools/testing/selftests/memory-hotplug/on-off-test.sh index a2816f631542..6cddde0b96f8 100644 --- a/tools/testing/selftests/memory-hotplug/on-off-test.sh +++ b/tools/testing/selftests/memory-hotplug/on-off-test.sh @@ -142,10 +142,16 @@ fi prerequisite +echo "Test scope: $ratio% hotplug memory" +echo -e "\t online all hotplug memory in offline state" +echo -e "\t offline $ratio% hotplug memory in online state" +echo -e "\t online all hotplug memory in offline state" + # # Online all hot-pluggable memory # for memory in `hotplaggable_offline_memory`; do + echo offline-online $memory online_memory_expect_success $memory done @@ -154,6 +160,7 @@ done # for memory in `hotpluggable_online_memory`; do if [ $((RANDOM % 100)) -lt $ratio ]; then + echo online-offline $memory offline_memory_expect_success $memory fi done @@ -162,6 +169,7 @@ done # Online all hot-pluggable memory again # for memory in `hotplaggable_offline_memory`; do + echo offline-online $memory online_memory_expect_success $memory done -- cgit v1.2.3 From a8035843770f34392bbadf0ba81ffa31ecb1209b Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:19 -0700 Subject: misc: mic: Add mic bus and dma driver documentation Added an overview of mic bus and dma driver. Reviewed-by: Ashutosh Dixit Reviewed-by: Nikhil Rao Reviewed-by: Sudeep Dutt Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- Documentation/mic/mic_overview.txt | 67 +++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 26 deletions(-) diff --git a/Documentation/mic/mic_overview.txt b/Documentation/mic/mic_overview.txt index b41929224804..77c541802ad9 100644 --- a/Documentation/mic/mic_overview.txt +++ b/Documentation/mic/mic_overview.txt @@ -17,35 +17,50 @@ for applications. A key benefit of our solution is that it leverages the standard virtio framework for network, disk and console devices, though in our case the virtio framework is used across a PCIe bus. +MIC PCIe card has a dma controller with 8 channels. These channels are +shared between the host s/w and the card s/w. 0 to 3 are used by host +and 4 to 7 by card. As the dma device doesn't show up as PCIe device, +a virtual bus called mic bus is created and virtual dma devices are +created on it by the host/card drivers. On host the channels are private +and used only by the host driver to transfer data for the virtio devices. + Here is a block diagram of the various components described above. The virtio backends are situated on the host rather than the card given better single threaded performance for the host compared to MIC, the ability of the host to initiate DMA's to/from the card using the MIC DMA engine and the fact that the virtio block storage backend can only be on the host. - | - +----------+ | +----------+ - | Card OS | | | Host OS | - +----------+ | +----------+ - | -+-------+ +--------+ +------+ | +---------+ +--------+ +--------+ -| Virtio| |Virtio | |Virtio| | |Virtio | |Virtio | |Virtio | -| Net | |Console | |Block | | |Net | |Console | |Block | -| Driver| |Driver | |Driver| | |backend | |backend | |backend | -+-------+ +--------+ +------+ | +---------+ +--------+ +--------+ - | | | | | | | - | | | |User | | | - | | | |------|------------|---------|------- - +-------------------+ |Kernel +--------------------------+ - | | | Virtio over PCIe IOCTLs | - | | +--------------------------+ - +--------------+ | | - |Intel MIC | | +---------------+ - |Card Driver | | |Intel MIC | - +--------------+ | |Host Driver | - | | +---------------+ - | | | - +-------------------------------------------------------------+ - | | - | PCIe Bus | - +-------------------------------------------------------------+ + | + +----------+ | +----------+ + | Card OS | | | Host OS | + +----------+ | +----------+ + | + +-------+ +--------+ +------+ | +---------+ +--------+ +--------+ + | Virtio| |Virtio | |Virtio| | |Virtio | |Virtio | |Virtio | + | Net | |Console | |Block | | |Net | |Console | |Block | + | Driver| |Driver | |Driver| | |backend | |backend | |backend | + +-------+ +--------+ +------+ | +---------+ +--------+ +--------+ + | | | | | | | + | | | |User | | | + | | | |------|------------|---------|------- + +-------------------+ |Kernel +--------------------------+ + | | | Virtio over PCIe IOCTLs | + | | +--------------------------+ ++-----------+ | | | +-----------+ +| MIC DMA | | | | | MIC DMA | +| Driver | | | | | Driver | ++-----------+ | | | +-----------+ + | | | | | ++---------------+ | | | +----------------+ +|MIC virtual Bus| | | | |MIC virtual Bus | ++---------------+ | | | +----------------+ + | | | | | + | +--------------+ | +---------------+ | + | |Intel MIC | | |Intel MIC | | + +---|Card Driver | | |Host Driver | | + +--------------+ | +---------------+-----+ + | | | + +-------------------------------------------------------------+ + | | + | PCIe Bus | + +-------------------------------------------------------------+ -- cgit v1.2.3 From 726526c3552c5718d5aba11ac2e914b0081a5c88 Mon Sep 17 00:00:00 2001 From: Sudeep Dutt Date: Fri, 11 Jul 2014 14:04:20 -0700 Subject: misc: mic: add a bus driver for virtual MIC devices This MIC virtual bus driver takes the responsibility of creating all the virtual devices connected to the PCIe device on the host and the platform device on the card. The MIC bus hardware operations provide a way to abstract certain hardware details from the base physical devices. Examples of devices added on the MIC virtual bus include host DMA and card DMA. This abstraction enables using a common DMA driver on host and card. Reviewed-by: Ashutosh Dixit Reviewed-by: Nikhil Rao Signed-off-by: Sudeep Dutt Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/Kconfig | 17 ++++ drivers/misc/mic/Makefile | 1 + drivers/misc/mic/bus/Makefile | 5 + drivers/misc/mic/bus/mic_bus.c | 218 +++++++++++++++++++++++++++++++++++++++++ include/linux/mic_bus.h | 110 +++++++++++++++++++++ 5 files changed, 351 insertions(+) create mode 100644 drivers/misc/mic/bus/Makefile create mode 100644 drivers/misc/mic/bus/mic_bus.c create mode 100644 include/linux/mic_bus.h diff --git a/drivers/misc/mic/Kconfig b/drivers/misc/mic/Kconfig index 462a5b1d8651..ee1d2ac3cd09 100644 --- a/drivers/misc/mic/Kconfig +++ b/drivers/misc/mic/Kconfig @@ -1,3 +1,20 @@ +comment "Intel MIC Bus Driver" + +config INTEL_MIC_BUS + tristate "Intel MIC Bus Driver" + depends on 64BIT && PCI && X86 && X86_DEV_DMA_OPS + help + This option is selected by any driver which registers a + device or driver on the MIC Bus, such as CONFIG_INTEL_MIC_HOST, + CONFIG_INTEL_MIC_CARD, CONFIG_INTEL_MIC_X100_DMA etc. + + If you are building a host/card kernel with an Intel MIC device + then say M (recommended) or Y, else say N. If unsure say N. + + More information about the Intel MIC family as well as the Linux + OS and tools for MIC to use with this driver are available from + . + comment "Intel MIC Host Driver" config INTEL_MIC_HOST diff --git a/drivers/misc/mic/Makefile b/drivers/misc/mic/Makefile index 05b34d683a58..e9bf148755e2 100644 --- a/drivers/misc/mic/Makefile +++ b/drivers/misc/mic/Makefile @@ -4,3 +4,4 @@ # obj-$(CONFIG_INTEL_MIC_HOST) += host/ obj-$(CONFIG_INTEL_MIC_CARD) += card/ +obj-$(CONFIG_INTEL_MIC_BUS) += bus/ diff --git a/drivers/misc/mic/bus/Makefile b/drivers/misc/mic/bus/Makefile new file mode 100644 index 000000000000..d85c7f2a0af4 --- /dev/null +++ b/drivers/misc/mic/bus/Makefile @@ -0,0 +1,5 @@ +# +# Makefile - Intel MIC Linux driver. +# Copyright(c) 2014, Intel Corporation. +# +obj-$(CONFIG_INTEL_MIC_BUS) += mic_bus.o diff --git a/drivers/misc/mic/bus/mic_bus.c b/drivers/misc/mic/bus/mic_bus.c new file mode 100644 index 000000000000..961ae90aae47 --- /dev/null +++ b/drivers/misc/mic/bus/mic_bus.c @@ -0,0 +1,218 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC Bus driver. + * + * This implementation is very similar to the the virtio bus driver + * implementation @ drivers/virtio/virtio.c + */ +#include +#include +#include +#include + +/* Unique numbering for mbus devices. */ +static DEFINE_IDA(mbus_index_ida); + +static ssize_t device_show(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct mbus_device *dev = dev_to_mbus(d); + return sprintf(buf, "0x%04x\n", dev->id.device); +} +static DEVICE_ATTR_RO(device); + +static ssize_t vendor_show(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct mbus_device *dev = dev_to_mbus(d); + return sprintf(buf, "0x%04x\n", dev->id.vendor); +} +static DEVICE_ATTR_RO(vendor); + +static ssize_t modalias_show(struct device *d, + struct device_attribute *attr, char *buf) +{ + struct mbus_device *dev = dev_to_mbus(d); + return sprintf(buf, "mbus:d%08Xv%08X\n", + dev->id.device, dev->id.vendor); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *mbus_dev_attrs[] = { + &dev_attr_device.attr, + &dev_attr_vendor.attr, + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mbus_dev); + +static inline int mbus_id_match(const struct mbus_device *dev, + const struct mbus_device_id *id) +{ + if (id->device != dev->id.device && id->device != MBUS_DEV_ANY_ID) + return 0; + + return id->vendor == MBUS_DEV_ANY_ID || id->vendor == dev->id.vendor; +} + +/* + * This looks through all the IDs a driver claims to support. If any of them + * match, we return 1 and the kernel will call mbus_dev_probe(). + */ +static int mbus_dev_match(struct device *dv, struct device_driver *dr) +{ + unsigned int i; + struct mbus_device *dev = dev_to_mbus(dv); + const struct mbus_device_id *ids; + + ids = drv_to_mbus(dr)->id_table; + for (i = 0; ids[i].device; i++) + if (mbus_id_match(dev, &ids[i])) + return 1; + return 0; +} + +static int mbus_uevent(struct device *dv, struct kobj_uevent_env *env) +{ + struct mbus_device *dev = dev_to_mbus(dv); + + return add_uevent_var(env, "MODALIAS=mbus:d%08Xv%08X", + dev->id.device, dev->id.vendor); +} + +static int mbus_dev_probe(struct device *d) +{ + int err; + struct mbus_device *dev = dev_to_mbus(d); + struct mbus_driver *drv = drv_to_mbus(dev->dev.driver); + + err = drv->probe(dev); + if (!err) + if (drv->scan) + drv->scan(dev); + return err; +} + +static int mbus_dev_remove(struct device *d) +{ + struct mbus_device *dev = dev_to_mbus(d); + struct mbus_driver *drv = drv_to_mbus(dev->dev.driver); + + drv->remove(dev); + return 0; +} + +static struct bus_type mic_bus = { + .name = "mic_bus", + .match = mbus_dev_match, + .dev_groups = mbus_dev_groups, + .uevent = mbus_uevent, + .probe = mbus_dev_probe, + .remove = mbus_dev_remove, +}; + +int mbus_register_driver(struct mbus_driver *driver) +{ + driver->driver.bus = &mic_bus; + return driver_register(&driver->driver); +} +EXPORT_SYMBOL_GPL(mbus_register_driver); + +void mbus_unregister_driver(struct mbus_driver *driver) +{ + driver_unregister(&driver->driver); +} +EXPORT_SYMBOL_GPL(mbus_unregister_driver); + +static void mbus_release_dev(struct device *d) +{ + struct mbus_device *mbdev = dev_to_mbus(d); + kfree(mbdev); +} + +struct mbus_device * +mbus_register_device(struct device *pdev, int id, struct dma_map_ops *dma_ops, + struct mbus_hw_ops *hw_ops, void __iomem *mmio_va) +{ + int ret; + struct mbus_device *mbdev; + + mbdev = kzalloc(sizeof(*mbdev), GFP_KERNEL); + if (!mbdev) + return ERR_PTR(-ENOMEM); + + mbdev->mmio_va = mmio_va; + mbdev->dev.parent = pdev; + mbdev->id.device = id; + mbdev->id.vendor = MBUS_DEV_ANY_ID; + mbdev->dev.archdata.dma_ops = dma_ops; + mbdev->dev.dma_mask = &mbdev->dev.coherent_dma_mask; + dma_set_mask(&mbdev->dev, DMA_BIT_MASK(64)); + mbdev->dev.release = mbus_release_dev; + mbdev->hw_ops = hw_ops; + mbdev->dev.bus = &mic_bus; + + /* Assign a unique device index and hence name. */ + ret = ida_simple_get(&mbus_index_ida, 0, 0, GFP_KERNEL); + if (ret < 0) + goto free_mbdev; + + mbdev->index = ret; + dev_set_name(&mbdev->dev, "mbus-dev%u", mbdev->index); + /* + * device_register() causes the bus infrastructure to look for a + * matching driver. + */ + ret = device_register(&mbdev->dev); + if (ret) + goto ida_remove; + return mbdev; +ida_remove: + ida_simple_remove(&mbus_index_ida, mbdev->index); +free_mbdev: + kfree(mbdev); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(mbus_register_device); + +void mbus_unregister_device(struct mbus_device *mbdev) +{ + int index = mbdev->index; /* save for after device release */ + + device_unregister(&mbdev->dev); + ida_simple_remove(&mbus_index_ida, index); +} +EXPORT_SYMBOL_GPL(mbus_unregister_device); + +static int __init mbus_init(void) +{ + return bus_register(&mic_bus); +} + +static void __exit mbus_exit(void) +{ + bus_unregister(&mic_bus); + ida_destroy(&mbus_index_ida); +} + +core_initcall(mbus_init); +module_exit(mbus_exit); + +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) MIC Bus driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mic_bus.h b/include/linux/mic_bus.h new file mode 100644 index 000000000000..d5b5f76d57ef --- /dev/null +++ b/include/linux/mic_bus.h @@ -0,0 +1,110 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC Bus driver. + * + * This implementation is very similar to the the virtio bus driver + * implementation @ include/linux/virtio.h. + */ +#ifndef _MIC_BUS_H_ +#define _MIC_BUS_H_ +/* + * Everything a mbus driver needs to work with any particular mbus + * implementation. + */ +#include +#include + +struct mbus_device_id { + __u32 device; + __u32 vendor; +}; + +#define MBUS_DEV_DMA_HOST 2 +#define MBUS_DEV_DMA_MIC 3 +#define MBUS_DEV_ANY_ID 0xffffffff + +/** + * mbus_device - representation of a device using mbus + * @mmio_va: virtual address of mmio space + * @hw_ops: the hardware ops supported by this device. + * @id: the device type identification (used to match it with a driver). + * @dev: underlying device. + * be used to communicate with. + * @index: unique position on the mbus bus + */ +struct mbus_device { + void __iomem *mmio_va; + struct mbus_hw_ops *hw_ops; + struct mbus_device_id id; + struct device dev; + int index; +}; + +/** + * mbus_driver - operations for a mbus I/O driver + * @driver: underlying device driver (populate name and owner). + * @id_table: the ids serviced by this driver. + * @probe: the function to call when a device is found. Returns 0 or -errno. + * @remove: the function to call when a device is removed. + */ +struct mbus_driver { + struct device_driver driver; + const struct mbus_device_id *id_table; + int (*probe)(struct mbus_device *dev); + void (*scan)(struct mbus_device *dev); + void (*remove)(struct mbus_device *dev); +}; + +/** + * struct mic_irq - opaque pointer used as cookie + */ +struct mic_irq; + +/** + * mbus_hw_ops - Hardware operations for accessing a MIC device on the MIC bus. + */ +struct mbus_hw_ops { + struct mic_irq* (*request_threaded_irq)(struct mbus_device *mbdev, + irq_handler_t handler, + irq_handler_t thread_fn, + const char *name, void *data, + int intr_src); + void (*free_irq)(struct mbus_device *mbdev, + struct mic_irq *cookie, void *data); + void (*ack_interrupt)(struct mbus_device *mbdev, int num); +}; + +struct mbus_device * +mbus_register_device(struct device *pdev, int id, struct dma_map_ops *dma_ops, + struct mbus_hw_ops *hw_ops, void __iomem *mmio_va); +void mbus_unregister_device(struct mbus_device *mbdev); + +int mbus_register_driver(struct mbus_driver *drv); +void mbus_unregister_driver(struct mbus_driver *drv); + +static inline struct mbus_device *dev_to_mbus(struct device *_dev) +{ + return container_of(_dev, struct mbus_device, dev); +} + +static inline struct mbus_driver *drv_to_mbus(struct device_driver *drv) +{ + return container_of(drv, struct mbus_driver, driver); +} + +#endif /* _MIC_BUS_H */ -- cgit v1.2.3 From b8e439f48a19e5832c08b4656a3a8ebb1f4b05f8 Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:22 -0700 Subject: misc: mic: add threaded irq support in host driver Convert mic_request_irq to mic_request_threaded_irq to support threaded irq for virtual devices on mic bus. Reviewed-by: Ashutosh Dixit Reviewed-by: Nikhil Rao Reviewed-by: Sudeep Dutt Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/host/mic_intr.c | 121 ++++++++++++++++++++++--------------- drivers/misc/mic/host/mic_intr.h | 24 +++++--- drivers/misc/mic/host/mic_main.c | 5 +- drivers/misc/mic/host/mic_virtio.c | 6 +- 4 files changed, 96 insertions(+), 60 deletions(-) diff --git a/drivers/misc/mic/host/mic_intr.c b/drivers/misc/mic/host/mic_intr.c index dbc5afde1392..d686f2846ac7 100644 --- a/drivers/misc/mic/host/mic_intr.c +++ b/drivers/misc/mic/host/mic_intr.c @@ -24,28 +24,29 @@ #include "../common/mic_dev.h" #include "mic_device.h" -/* - * mic_invoke_callback - Invoke callback functions registered for - * the corresponding source id. - * - * @mdev: pointer to the mic_device instance - * @idx: The interrupt source id. - * - * Returns none. - */ -static inline void mic_invoke_callback(struct mic_device *mdev, int idx) +static irqreturn_t mic_thread_fn(int irq, void *dev) { + struct mic_device *mdev = dev; + struct mic_intr_info *intr_info = mdev->intr_info; + struct mic_irq_info *irq_info = &mdev->irq_info; struct mic_intr_cb *intr_cb; struct pci_dev *pdev = container_of(mdev->sdev->parent, - struct pci_dev, dev); + struct pci_dev, dev); + int i; - spin_lock(&mdev->irq_info.mic_intr_lock); - list_for_each_entry(intr_cb, &mdev->irq_info.cb_list[idx], list) - if (intr_cb->func) - intr_cb->func(pdev->irq, intr_cb->data); - spin_unlock(&mdev->irq_info.mic_intr_lock); + spin_lock(&irq_info->mic_thread_lock); + for (i = intr_info->intr_start_idx[MIC_INTR_DB]; + i < intr_info->intr_len[MIC_INTR_DB]; i++) + if (test_and_clear_bit(i, &irq_info->mask)) { + list_for_each_entry(intr_cb, &irq_info->cb_list[i], + list) + if (intr_cb->thread_fn) + intr_cb->thread_fn(pdev->irq, + intr_cb->data); + } + spin_unlock(&irq_info->mic_thread_lock); + return IRQ_HANDLED; } - /** * mic_interrupt - Generic interrupt handler for * MSI and INTx based interrupts. @@ -53,7 +54,11 @@ static inline void mic_invoke_callback(struct mic_device *mdev, int idx) static irqreturn_t mic_interrupt(int irq, void *dev) { struct mic_device *mdev = dev; - struct mic_intr_info *info = mdev->intr_info; + struct mic_intr_info *intr_info = mdev->intr_info; + struct mic_irq_info *irq_info = &mdev->irq_info; + struct mic_intr_cb *intr_cb; + struct pci_dev *pdev = container_of(mdev->sdev->parent, + struct pci_dev, dev); u32 mask; int i; @@ -61,12 +66,19 @@ static irqreturn_t mic_interrupt(int irq, void *dev) if (!mask) return IRQ_NONE; - for (i = info->intr_start_idx[MIC_INTR_DB]; - i < info->intr_len[MIC_INTR_DB]; i++) - if (mask & BIT(i)) - mic_invoke_callback(mdev, i); - - return IRQ_HANDLED; + spin_lock(&irq_info->mic_intr_lock); + for (i = intr_info->intr_start_idx[MIC_INTR_DB]; + i < intr_info->intr_len[MIC_INTR_DB]; i++) + if (mask & BIT(i)) { + list_for_each_entry(intr_cb, &irq_info->cb_list[i], + list) + if (intr_cb->handler) + intr_cb->handler(pdev->irq, + intr_cb->data); + set_bit(i, &irq_info->mask); + } + spin_unlock(&irq_info->mic_intr_lock); + return IRQ_WAKE_THREAD; } /* Return the interrupt offset from the index. Index is 0 based. */ @@ -99,14 +111,15 @@ static struct msix_entry *mic_get_available_vector(struct mic_device *mdev) * * @mdev: pointer to the mic_device instance * @idx: The source id to be registered. - * @func: The function to be called when the source id receives + * @handler: The function to be called when the source id receives * the interrupt. + * @thread_fn: thread fn. corresponding to the handler * @data: Private data of the requester. * Return the callback structure that was registered or an * appropriate error on failure. */ static struct mic_intr_cb *mic_register_intr_callback(struct mic_device *mdev, - u8 idx, irqreturn_t (*func) (int irq, void *dev), + u8 idx, irq_handler_t handler, irq_handler_t thread_fn, void *data) { struct mic_intr_cb *intr_cb; @@ -117,7 +130,8 @@ static struct mic_intr_cb *mic_register_intr_callback(struct mic_device *mdev, if (!intr_cb) return ERR_PTR(-ENOMEM); - intr_cb->func = func; + intr_cb->handler = handler; + intr_cb->thread_fn = thread_fn; intr_cb->data = data; intr_cb->cb_id = ida_simple_get(&mdev->irq_info.cb_ida, 0, 0, GFP_KERNEL); @@ -126,9 +140,11 @@ static struct mic_intr_cb *mic_register_intr_callback(struct mic_device *mdev, goto ida_fail; } + spin_lock(&mdev->irq_info.mic_thread_lock); spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); list_add_tail(&intr_cb->list, &mdev->irq_info.cb_list[idx]); spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags); + spin_unlock(&mdev->irq_info.mic_thread_lock); return intr_cb; ida_fail: @@ -152,8 +168,9 @@ static u8 mic_unregister_intr_callback(struct mic_device *mdev, u32 idx) unsigned long flags; int i; + spin_lock(&mdev->irq_info.mic_thread_lock); + spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); for (i = 0; i < MIC_NUM_OFFSETS; i++) { - spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); list_for_each_safe(pos, tmp, &mdev->irq_info.cb_list[i]) { intr_cb = list_entry(pos, struct mic_intr_cb, list); if (intr_cb->cb_id == idx) { @@ -163,11 +180,13 @@ static u8 mic_unregister_intr_callback(struct mic_device *mdev, u32 idx) kfree(intr_cb); spin_unlock_irqrestore( &mdev->irq_info.mic_intr_lock, flags); + spin_unlock(&mdev->irq_info.mic_thread_lock); return i; } } - spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags); } + spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags); + spin_unlock(&mdev->irq_info.mic_thread_lock); return MIC_NUM_OFFSETS; } @@ -242,6 +261,7 @@ static int mic_setup_callbacks(struct mic_device *mdev) INIT_LIST_HEAD(&mdev->irq_info.cb_list[i]); ida_init(&mdev->irq_info.cb_ida); spin_lock_init(&mdev->irq_info.mic_intr_lock); + spin_lock_init(&mdev->irq_info.mic_thread_lock); return 0; } @@ -258,14 +278,12 @@ static void mic_release_callbacks(struct mic_device *mdev) struct mic_intr_cb *intr_cb; int i; + spin_lock(&mdev->irq_info.mic_thread_lock); + spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); for (i = 0; i < MIC_NUM_OFFSETS; i++) { - spin_lock_irqsave(&mdev->irq_info.mic_intr_lock, flags); - if (list_empty(&mdev->irq_info.cb_list[i])) { - spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, - flags); + if (list_empty(&mdev->irq_info.cb_list[i])) break; - } list_for_each_safe(pos, tmp, &mdev->irq_info.cb_list[i]) { intr_cb = list_entry(pos, struct mic_intr_cb, list); @@ -274,8 +292,9 @@ static void mic_release_callbacks(struct mic_device *mdev) intr_cb->cb_id); kfree(intr_cb); } - spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags); } + spin_unlock_irqrestore(&mdev->irq_info.mic_intr_lock, flags); + spin_unlock(&mdev->irq_info.mic_thread_lock); ida_destroy(&mdev->irq_info.cb_ida); kfree(mdev->irq_info.cb_list); } @@ -313,7 +332,8 @@ static int mic_setup_msi(struct mic_device *mdev, struct pci_dev *pdev) goto err_nomem2; } - rc = request_irq(pdev->irq, mic_interrupt, 0 , "mic-msi", mdev); + rc = request_threaded_irq(pdev->irq, mic_interrupt, mic_thread_fn, + 0, "mic-msi", mdev); if (rc) { dev_err(&pdev->dev, "Error allocating MSI interrupt\n"); goto err_irq_req_fail; @@ -353,8 +373,8 @@ static int mic_setup_intx(struct mic_device *mdev, struct pci_dev *pdev) goto err_nomem; } - rc = request_irq(pdev->irq, mic_interrupt, - IRQF_SHARED, "mic-intx", mdev); + rc = request_threaded_irq(pdev->irq, mic_interrupt, mic_thread_fn, + IRQF_SHARED, "mic-intx", mdev); if (rc) goto err; @@ -391,13 +411,14 @@ int mic_next_db(struct mic_device *mdev) #define MK_COOKIE(x, y) ((x) | (y) << COOKIE_ID_SHIFT) /** - * mic_request_irq - request an irq. mic_mutex needs + * mic_request_threaded_irq - request an irq. mic_mutex needs * to be held before calling this function. * * @mdev: pointer to mic_device instance - * @func: The callback function that handles the interrupt. + * @handler: The callback function that handles the interrupt. * The function needs to call ack_interrupts * (mdev->ops->ack_interrupt(mdev)) when handling the interrupts. + * @thread_fn: thread fn required by request_threaded_irq. * @name: The ASCII name of the callee requesting the irq. * @data: private data that is returned back when calling the * function handler. @@ -412,10 +433,11 @@ int mic_next_db(struct mic_device *mdev) * error code. * */ -struct mic_irq *mic_request_irq(struct mic_device *mdev, - irqreturn_t (*func)(int irq, void *dev), - const char *name, void *data, int intr_src, - enum mic_intr_type type) +struct mic_irq * +mic_request_threaded_irq(struct mic_device *mdev, + irq_handler_t handler, irq_handler_t thread_fn, + const char *name, void *data, int intr_src, + enum mic_intr_type type) { u16 offset; int rc = 0; @@ -444,7 +466,8 @@ struct mic_irq *mic_request_irq(struct mic_device *mdev, goto err; } - rc = request_irq(msix->vector, func, 0, name, data); + rc = request_threaded_irq(msix->vector, handler, thread_fn, + 0, name, data); if (rc) { dev_dbg(mdev->sdev->parent, "request irq failed rc = %d\n", rc); @@ -458,8 +481,8 @@ struct mic_irq *mic_request_irq(struct mic_device *mdev, dev_dbg(mdev->sdev->parent, "irq: %d assigned for src: %d\n", msix->vector, intr_src); } else { - intr_cb = mic_register_intr_callback(mdev, - offset, func, data); + intr_cb = mic_register_intr_callback(mdev, offset, handler, + thread_fn, data); if (IS_ERR(intr_cb)) { dev_err(mdev->sdev->parent, "No available callback entries for use\n"); @@ -487,9 +510,9 @@ err: * needs to be held before calling this function. * * @mdev: pointer to mic_device instance - * @cookie: cookie obtained during a successful call to mic_request_irq + * @cookie: cookie obtained during a successful call to mic_request_threaded_irq * @data: private data specified by the calling function during the - * mic_request_irq + * mic_request_threaded_irq * * returns: none. */ diff --git a/drivers/misc/mic/host/mic_intr.h b/drivers/misc/mic/host/mic_intr.h index 6091aa97e116..b1334dd9a0a1 100644 --- a/drivers/misc/mic/host/mic_intr.h +++ b/drivers/misc/mic/host/mic_intr.h @@ -21,6 +21,8 @@ #ifndef _MIC_INTR_H_ #define _MIC_INTR_H_ +#include +#include /* * The minimum number of msix vectors required for normal operation. * 3 for virtio network, console and block devices. @@ -68,7 +70,11 @@ struct mic_intr_info { * @num_vectors: The number of MSI/MSI-x vectors that have been allocated. * @cb_ida: callback ID allocator to track the callbacks registered. * @mic_intr_lock: spinlock to protect the interrupt callback list. + * @mic_thread_lock: spinlock to protect the thread callback list. + * This lock is used to protect against thread_fn while + * mic_intr_lock is used to protect against interrupt handler. * @cb_list: Array of callback lists one for each source. + * @mask: Mask used by the main thread fn to call the underlying thread fns. */ struct mic_irq_info { int next_avail_src; @@ -77,19 +83,23 @@ struct mic_irq_info { u16 num_vectors; struct ida cb_ida; spinlock_t mic_intr_lock; + spinlock_t mic_thread_lock; struct list_head *cb_list; + unsigned long mask; }; /** * struct mic_intr_cb - Interrupt callback structure. * - * @func: The callback function + * @handler: The callback function + * @thread_fn: The thread_fn. * @data: Private data of the requester. * @cb_id: The callback id. Identifies this callback. * @list: list head pointing to the next callback structure. */ struct mic_intr_cb { - irqreturn_t (*func) (int irq, void *data); + irq_handler_t handler; + irq_handler_t thread_fn; void *data; int cb_id; struct list_head list; @@ -124,11 +134,11 @@ struct mic_hw_intr_ops { }; int mic_next_db(struct mic_device *mdev); -struct mic_irq *mic_request_irq(struct mic_device *mdev, - irqreturn_t (*func)(int irq, void *data), - const char *name, void *data, int intr_src, - enum mic_intr_type type); - +struct mic_irq * +mic_request_threaded_irq(struct mic_device *mdev, + irq_handler_t handler, irq_handler_t thread_fn, + const char *name, void *data, int intr_src, + enum mic_intr_type type); void mic_free_irq(struct mic_device *mdev, struct mic_irq *cookie, void *data); int mic_setup_interrupts(struct mic_device *mdev, struct pci_dev *pdev); diff --git a/drivers/misc/mic/host/mic_main.c b/drivers/misc/mic/host/mic_main.c index c04a021e20c7..fdc9c13430e7 100644 --- a/drivers/misc/mic/host/mic_main.c +++ b/drivers/misc/mic/host/mic_main.c @@ -389,8 +389,9 @@ static int mic_probe(struct pci_dev *pdev, mutex_lock(&mdev->mic_mutex); mdev->shutdown_db = mic_next_db(mdev); - mdev->shutdown_cookie = mic_request_irq(mdev, mic_shutdown_db, - "shutdown-interrupt", mdev, mdev->shutdown_db, MIC_INTR_DB); + mdev->shutdown_cookie = mic_request_threaded_irq(mdev, mic_shutdown_db, + NULL, "shutdown-interrupt", mdev, + mdev->shutdown_db, MIC_INTR_DB); if (IS_ERR(mdev->shutdown_cookie)) { rc = PTR_ERR(mdev->shutdown_cookie); mutex_unlock(&mdev->mic_mutex); diff --git a/drivers/misc/mic/host/mic_virtio.c b/drivers/misc/mic/host/mic_virtio.c index 7e1ef0ebbb80..aba3e8324b82 100644 --- a/drivers/misc/mic/host/mic_virtio.c +++ b/drivers/misc/mic/host/mic_virtio.c @@ -594,8 +594,10 @@ int mic_virtio_add_device(struct mic_vdev *mvdev, snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id, mvdev->virtio_id); mvdev->virtio_db = mic_next_db(mdev); - mvdev->virtio_cookie = mic_request_irq(mdev, mic_virtio_intr_handler, - irqname, mvdev, mvdev->virtio_db, MIC_INTR_DB); + mvdev->virtio_cookie = mic_request_threaded_irq(mdev, + mic_virtio_intr_handler, + NULL, irqname, mvdev, + mvdev->virtio_db, MIC_INTR_DB); if (IS_ERR(mvdev->virtio_cookie)) { ret = PTR_ERR(mvdev->virtio_cookie); dev_dbg(mdev->sdev->parent, "request irq failed\n"); -- cgit v1.2.3 From d4ef098e4cd836b3726781eabe064d7010b6eaa8 Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:23 -0700 Subject: misc: mic: add dma support in host driver This patch adds a dma device on the mic virtual bus and uses this dmaengine to transfer data for virtio devices Reviewed-by: Nikhil Rao Signed-off-by: Sudeep Dutt Signed-off-by: Ashutosh Dixit Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/Kconfig | 2 +- drivers/misc/mic/host/mic_boot.c | 83 ++++++++++++++++- drivers/misc/mic/host/mic_device.h | 24 +++++ drivers/misc/mic/host/mic_intr.h | 3 +- drivers/misc/mic/host/mic_virtio.c | 181 +++++++++++++++++++++++++++++-------- drivers/misc/mic/host/mic_virtio.h | 21 ++++- drivers/misc/mic/host/mic_x100.c | 8 ++ 7 files changed, 281 insertions(+), 41 deletions(-) diff --git a/drivers/misc/mic/Kconfig b/drivers/misc/mic/Kconfig index ee1d2ac3cd09..bf76313dab16 100644 --- a/drivers/misc/mic/Kconfig +++ b/drivers/misc/mic/Kconfig @@ -19,7 +19,7 @@ comment "Intel MIC Host Driver" config INTEL_MIC_HOST tristate "Intel MIC Host Driver" - depends on 64BIT && PCI && X86 + depends on 64BIT && PCI && X86 && INTEL_MIC_BUS select VHOST_RING help This enables Host Driver support for the Intel Many Integrated diff --git a/drivers/misc/mic/host/mic_boot.c b/drivers/misc/mic/host/mic_boot.c index b75c6b5cc20f..ff2b0fb1a6be 100644 --- a/drivers/misc/mic/host/mic_boot.c +++ b/drivers/misc/mic/host/mic_boot.c @@ -23,11 +23,70 @@ #include #include +#include #include "../common/mic_dev.h" #include "mic_device.h" #include "mic_smpt.h" #include "mic_virtio.h" +static inline struct mic_device *mbdev_to_mdev(struct mbus_device *mbdev) +{ + return dev_get_drvdata(mbdev->dev.parent); +} + +static dma_addr_t +mic_dma_map_page(struct device *dev, struct page *page, + unsigned long offset, size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + void *va = phys_to_virt(page_to_phys(page)) + offset; + struct mic_device *mdev = dev_get_drvdata(dev->parent); + + return mic_map_single(mdev, va, size); +} + +static void +mic_dma_unmap_page(struct device *dev, dma_addr_t dma_addr, + size_t size, enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + struct mic_device *mdev = dev_get_drvdata(dev->parent); + mic_unmap_single(mdev, dma_addr, size); +} + +static struct dma_map_ops mic_dma_ops = { + .map_page = mic_dma_map_page, + .unmap_page = mic_dma_unmap_page, +}; + +static struct mic_irq * +_mic_request_threaded_irq(struct mbus_device *mbdev, + irq_handler_t handler, irq_handler_t thread_fn, + const char *name, void *data, int intr_src) +{ + return mic_request_threaded_irq(mbdev_to_mdev(mbdev), handler, + thread_fn, name, data, + intr_src, MIC_INTR_DMA); +} + +static void _mic_free_irq(struct mbus_device *mbdev, + struct mic_irq *cookie, void *data) +{ + return mic_free_irq(mbdev_to_mdev(mbdev), cookie, data); +} + +static void _mic_ack_interrupt(struct mbus_device *mbdev, int num) +{ + struct mic_device *mdev = mbdev_to_mdev(mbdev); + mdev->ops->intr_workarounds(mdev); +} + +static struct mbus_hw_ops mbus_hw_ops = { + .request_threaded_irq = _mic_request_threaded_irq, + .free_irq = _mic_free_irq, + .ack_interrupt = _mic_ack_interrupt, +}; + /** * mic_reset - Reset the MIC device. * @mdev: pointer to mic_device instance @@ -95,9 +154,21 @@ retry: */ goto retry; } + mdev->dma_mbdev = mbus_register_device(mdev->sdev->parent, + MBUS_DEV_DMA_HOST, &mic_dma_ops, + &mbus_hw_ops, mdev->mmio.va); + if (IS_ERR(mdev->dma_mbdev)) { + rc = PTR_ERR(mdev->dma_mbdev); + goto unlock_ret; + } + mdev->dma_ch = mic_request_dma_chan(mdev); + if (!mdev->dma_ch) { + rc = -ENXIO; + goto dma_remove; + } rc = mdev->ops->load_mic_fw(mdev, buf); if (rc) - goto unlock_ret; + goto dma_release; mic_smpt_restore(mdev); mic_intr_restore(mdev); mdev->intr_ops->enable_interrupts(mdev); @@ -105,6 +176,11 @@ retry: mdev->ops->write_spad(mdev, MIC_DPHI_SPAD, mdev->dp_dma_addr >> 32); mdev->ops->send_firmware_intr(mdev); mic_set_state(mdev, MIC_ONLINE); + goto unlock_ret; +dma_release: + dma_release_channel(mdev->dma_ch); +dma_remove: + mbus_unregister_device(mdev->dma_mbdev); unlock_ret: mutex_unlock(&mdev->mic_mutex); return rc; @@ -122,6 +198,11 @@ void mic_stop(struct mic_device *mdev, bool force) mutex_lock(&mdev->mic_mutex); if (MIC_OFFLINE != mdev->state || force) { mic_virtio_reset_devices(mdev); + if (mdev->dma_ch) { + dma_release_channel(mdev->dma_ch); + mdev->dma_ch = NULL; + } + mbus_unregister_device(mdev->dma_mbdev); mic_bootparam_init(mdev); mic_reset(mdev); if (MIC_RESET_FAILED == mdev->state) diff --git a/drivers/misc/mic/host/mic_device.h b/drivers/misc/mic/host/mic_device.h index 0398c696d257..016bd15a7bd1 100644 --- a/drivers/misc/mic/host/mic_device.h +++ b/drivers/misc/mic/host/mic_device.h @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "mic_intr.h" @@ -87,6 +89,8 @@ enum mic_stepping { * @cdev: Character device for MIC. * @vdev_list: list of virtio devices. * @pm_notifier: Handles PM notifications from the OS. + * @dma_mbdev: MIC BUS DMA device. + * @dma_ch: DMA channel reserved by this driver for use by virtio devices. */ struct mic_device { struct mic_mw mmio; @@ -124,6 +128,8 @@ struct mic_device { struct cdev cdev; struct list_head vdev_list; struct notifier_block pm_notifier; + struct mbus_device *dma_mbdev; + struct dma_chan *dma_ch; }; /** @@ -144,6 +150,7 @@ struct mic_device { * @load_mic_fw: Load firmware segments required to boot the card * into card memory. This includes the kernel, command line, ramdisk etc. * @get_postcode: Get post code status from firmware. + * @dma_filter: DMA filter function to be used. */ struct mic_hw_ops { u8 aper_bar; @@ -159,6 +166,7 @@ struct mic_hw_ops { void (*send_firmware_intr)(struct mic_device *mdev); int (*load_mic_fw)(struct mic_device *mdev, const char *buf); u32 (*get_postcode)(struct mic_device *mdev); + bool (*dma_filter)(struct dma_chan *chan, void *param); }; /** @@ -187,6 +195,22 @@ mic_mmio_write(struct mic_mw *mw, u32 val, u32 offset) iowrite32(val, mw->va + offset); } +static inline struct dma_chan *mic_request_dma_chan(struct mic_device *mdev) +{ + dma_cap_mask_t mask; + struct dma_chan *chan; + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + chan = dma_request_channel(mask, mdev->ops->dma_filter, + mdev->sdev->parent); + if (chan) + return chan; + dev_err(mdev->sdev->parent, "%s %d unable to acquire channel\n", + __func__, __LINE__); + return NULL; +} + void mic_sysfs_init(struct mic_device *mdev); int mic_start(struct mic_device *mdev, const char *buf); void mic_stop(struct mic_device *mdev, bool force); diff --git a/drivers/misc/mic/host/mic_intr.h b/drivers/misc/mic/host/mic_intr.h index b1334dd9a0a1..9f783d4ad7f1 100644 --- a/drivers/misc/mic/host/mic_intr.h +++ b/drivers/misc/mic/host/mic_intr.h @@ -27,8 +27,9 @@ * The minimum number of msix vectors required for normal operation. * 3 for virtio network, console and block devices. * 1 for card shutdown notifications. + * 4 for host owned DMA channels. */ -#define MIC_MIN_MSIX 4 +#define MIC_MIN_MSIX 8 #define MIC_NUM_OFFSETS 32 /** diff --git a/drivers/misc/mic/host/mic_virtio.c b/drivers/misc/mic/host/mic_virtio.c index aba3e8324b82..a020e4eb435a 100644 --- a/drivers/misc/mic/host/mic_virtio.c +++ b/drivers/misc/mic/host/mic_virtio.c @@ -21,60 +21,157 @@ #include #include #include - +#include #include + #include "../common/mic_dev.h" #include "mic_device.h" #include "mic_smpt.h" #include "mic_virtio.h" /* - * Initiates the copies across the PCIe bus from card memory to - * a user space buffer. + * Size of the internal buffer used during DMA's as an intermediate buffer + * for copy to/from user. */ -static int mic_virtio_copy_to_user(struct mic_vdev *mvdev, - void __user *ubuf, size_t len, u64 addr) +#define MIC_INT_DMA_BUF_SIZE PAGE_ALIGN(64 * 1024ULL) + +static int mic_sync_dma(struct mic_device *mdev, dma_addr_t dst, + dma_addr_t src, size_t len) { - int err; - void __iomem *dbuf = mvdev->mdev->aper.va + addr; - /* - * We are copying from IO below an should ideally use something - * like copy_to_user_fromio(..) if it existed. - */ - if (copy_to_user(ubuf, (void __force *)dbuf, len)) { - err = -EFAULT; - dev_err(mic_dev(mvdev), "%s %d err %d\n", + int err = 0; + struct dma_async_tx_descriptor *tx; + struct dma_chan *mic_ch = mdev->dma_ch; + + if (!mic_ch) { + err = -EBUSY; + goto error; + } + + tx = mic_ch->device->device_prep_dma_memcpy(mic_ch, dst, src, len, + DMA_PREP_FENCE); + if (!tx) { + err = -ENOMEM; + goto error; + } else { + dma_cookie_t cookie = tx->tx_submit(tx); + + err = dma_submit_error(cookie); + if (err) + goto error; + err = dma_sync_wait(mic_ch, cookie); + } +error: + if (err) + dev_err(mdev->sdev->parent, "%s %d err %d\n", __func__, __LINE__, err); - goto err; + return err; +} + +/* + * Initiates the copies across the PCIe bus from card memory to a user + * space buffer. When transfers are done using DMA, source/destination + * addresses and transfer length must follow the alignment requirements of + * the MIC DMA engine. + */ +static int mic_virtio_copy_to_user(struct mic_vdev *mvdev, void __user *ubuf, + size_t len, u64 daddr, size_t dlen, + int vr_idx) +{ + struct mic_device *mdev = mvdev->mdev; + void __iomem *dbuf = mdev->aper.va + daddr; + struct mic_vringh *mvr = &mvdev->mvr[vr_idx]; + size_t dma_alignment = 1 << mdev->dma_ch->device->copy_align; + size_t dma_offset; + size_t partlen; + int err; + + dma_offset = daddr - round_down(daddr, dma_alignment); + daddr -= dma_offset; + len += dma_offset; + + while (len) { + partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE); + + err = mic_sync_dma(mdev, mvr->buf_da, daddr, + ALIGN(partlen, dma_alignment)); + if (err) + goto err; + + if (copy_to_user(ubuf, mvr->buf + dma_offset, + partlen - dma_offset)) { + err = -EFAULT; + goto err; + } + daddr += partlen; + ubuf += partlen; + dbuf += partlen; + mvdev->in_bytes_dma += partlen; + mvdev->in_bytes += partlen; + len -= partlen; + dma_offset = 0; } - mvdev->in_bytes += len; - err = 0; + return 0; err: + dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err); return err; } /* - * Initiates copies across the PCIe bus from a user space - * buffer to card memory. + * Initiates copies across the PCIe bus from a user space buffer to card + * memory. When transfers are done using DMA, source/destination addresses + * and transfer length must follow the alignment requirements of the MIC + * DMA engine. */ -static int mic_virtio_copy_from_user(struct mic_vdev *mvdev, - void __user *ubuf, size_t len, u64 addr) +static int mic_virtio_copy_from_user(struct mic_vdev *mvdev, void __user *ubuf, + size_t len, u64 daddr, size_t dlen, + int vr_idx) { + struct mic_device *mdev = mvdev->mdev; + void __iomem *dbuf = mdev->aper.va + daddr; + struct mic_vringh *mvr = &mvdev->mvr[vr_idx]; + size_t dma_alignment = 1 << mdev->dma_ch->device->copy_align; + size_t partlen; int err; - void __iomem *dbuf = mvdev->mdev->aper.va + addr; + + if (daddr & (dma_alignment - 1)) { + mvdev->tx_dst_unaligned += len; + goto memcpy; + } else if (ALIGN(len, dma_alignment) > dlen) { + mvdev->tx_len_unaligned += len; + goto memcpy; + } + + while (len) { + partlen = min_t(size_t, len, MIC_INT_DMA_BUF_SIZE); + + if (copy_from_user(mvr->buf, ubuf, partlen)) { + err = -EFAULT; + goto err; + } + err = mic_sync_dma(mdev, daddr, mvr->buf_da, + ALIGN(partlen, dma_alignment)); + if (err) + goto err; + daddr += partlen; + ubuf += partlen; + dbuf += partlen; + mvdev->out_bytes_dma += partlen; + mvdev->out_bytes += partlen; + len -= partlen; + } +memcpy: /* * We are copying to IO below and should ideally use something * like copy_from_user_toio(..) if it existed. */ if (copy_from_user((void __force *)dbuf, ubuf, len)) { err = -EFAULT; - dev_err(mic_dev(mvdev), "%s %d err %d\n", - __func__, __LINE__, err); goto err; } mvdev->out_bytes += len; - err = 0; + return 0; err: + dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, err); return err; } @@ -110,7 +207,8 @@ static inline u32 mic_vringh_iov_consumed(struct vringh_kiov *iov) * way to override the VRINGH xfer(..) routines as of v3.10. */ static int mic_vringh_copy(struct mic_vdev *mvdev, struct vringh_kiov *iov, - void __user *ubuf, size_t len, bool read, size_t *out_len) + void __user *ubuf, size_t len, bool read, int vr_idx, + size_t *out_len) { int ret = 0; size_t partlen, tot_len = 0; @@ -118,13 +216,15 @@ static int mic_vringh_copy(struct mic_vdev *mvdev, struct vringh_kiov *iov, while (len && iov->i < iov->used) { partlen = min(iov->iov[iov->i].iov_len, len); if (read) - ret = mic_virtio_copy_to_user(mvdev, - ubuf, partlen, - (u64)iov->iov[iov->i].iov_base); + ret = mic_virtio_copy_to_user(mvdev, ubuf, partlen, + (u64)iov->iov[iov->i].iov_base, + iov->iov[iov->i].iov_len, + vr_idx); else - ret = mic_virtio_copy_from_user(mvdev, - ubuf, partlen, - (u64)iov->iov[iov->i].iov_base); + ret = mic_virtio_copy_from_user(mvdev, ubuf, partlen, + (u64)iov->iov[iov->i].iov_base, + iov->iov[iov->i].iov_len, + vr_idx); if (ret) { dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, ret); @@ -192,8 +292,8 @@ static int _mic_virtio_copy(struct mic_vdev *mvdev, ubuf = iov.iov_base; } /* Issue all the read descriptors first */ - ret = mic_vringh_copy(mvdev, riov, ubuf, len, - MIC_VRINGH_READ, &out_len); + ret = mic_vringh_copy(mvdev, riov, ubuf, len, MIC_VRINGH_READ, + copy->vr_idx, &out_len); if (ret) { dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, ret); @@ -203,8 +303,8 @@ static int _mic_virtio_copy(struct mic_vdev *mvdev, ubuf += out_len; copy->out_len += out_len; /* Issue the write descriptors next */ - ret = mic_vringh_copy(mvdev, wiov, ubuf, len, - !MIC_VRINGH_READ, &out_len); + ret = mic_vringh_copy(mvdev, wiov, ubuf, len, !MIC_VRINGH_READ, + copy->vr_idx, &out_len); if (ret) { dev_err(mic_dev(mvdev), "%s %d err %d\n", __func__, __LINE__, ret); @@ -589,6 +689,10 @@ int mic_virtio_add_device(struct mic_vdev *mvdev, dev_dbg(mdev->sdev->parent, "%s %d index %d va %p info %p vr_size 0x%x\n", __func__, __LINE__, i, vr->va, vr->info, vr_size); + mvr->buf = (void *)__get_free_pages(GFP_KERNEL, + get_order(MIC_INT_DMA_BUF_SIZE)); + mvr->buf_da = mic_map_single(mvdev->mdev, mvr->buf, + MIC_INT_DMA_BUF_SIZE); } snprintf(irqname, sizeof(irqname), "mic%dvirtio%d", mdev->id, @@ -673,6 +777,11 @@ skip_hot_remove: vqconfig = mic_vq_config(mvdev->dd); for (i = 0; i < mvdev->dd->num_vq; i++) { struct mic_vringh *mvr = &mvdev->mvr[i]; + + mic_unmap_single(mvdev->mdev, mvr->buf_da, + MIC_INT_DMA_BUF_SIZE); + free_pages((unsigned long)mvr->buf, + get_order(MIC_INT_DMA_BUF_SIZE)); vringh_kiov_cleanup(&mvr->riov); vringh_kiov_cleanup(&mvr->wiov); mic_unmap_single(mdev, le64_to_cpu(vqconfig[i].address), diff --git a/drivers/misc/mic/host/mic_virtio.h b/drivers/misc/mic/host/mic_virtio.h index 184f3c84805b..d574efb853d9 100644 --- a/drivers/misc/mic/host/mic_virtio.h +++ b/drivers/misc/mic/host/mic_virtio.h @@ -46,18 +46,23 @@ * @vrh: The host VRINGH used for accessing the card vrings. * @riov: The VRINGH read kernel IOV. * @wiov: The VRINGH write kernel IOV. - * @head: The VRINGH head index address passed to vringh_getdesc_kern(..). * @vr_mutex: Mutex for synchronizing access to the VRING. + * @buf: Temporary kernel buffer used to copy in/out data + * from/to the card via DMA. + * @buf_da: dma address of buf. * @mvdev: Back pointer to MIC virtio device for vringh_notify(..). + * @head: The VRINGH head index address passed to vringh_getdesc_kern(..). */ struct mic_vringh { struct mic_vring vring; struct vringh vrh; struct vringh_kiov riov; struct vringh_kiov wiov; - u16 head; struct mutex vr_mutex; + void *buf; + dma_addr_t buf_da; struct mic_vdev *mvdev; + u16 head; }; /** @@ -69,6 +74,14 @@ struct mic_vringh { * @poll_wake - Used for waking up threads blocked in poll. * @out_bytes - Debug stats for number of bytes copied from host to card. * @in_bytes - Debug stats for number of bytes copied from card to host. + * @out_bytes_dma - Debug stats for number of bytes copied from host to card + * using DMA. + * @in_bytes_dma - Debug stats for number of bytes copied from card to host + * using DMA. + * @tx_len_unaligned - Debug stats for number of bytes copied to the card where + * the transfer length did not have the required DMA alignment. + * @tx_dst_unaligned - Debug stats for number of bytes copied where the + * destination address on the card did not have the required DMA alignment. * @mvr - Store per VRING data structures. * @virtio_bh_work - Work struct used to schedule virtio bottom half handling. * @dd - Virtio device descriptor. @@ -84,6 +97,10 @@ struct mic_vdev { int poll_wake; unsigned long out_bytes; unsigned long in_bytes; + unsigned long out_bytes_dma; + unsigned long in_bytes_dma; + unsigned long tx_len_unaligned; + unsigned long tx_dst_unaligned; struct mic_vringh mvr[MIC_MAX_VRINGS]; struct work_struct virtio_bh_work; struct mic_device_desc *dd; diff --git a/drivers/misc/mic/host/mic_x100.c b/drivers/misc/mic/host/mic_x100.c index 5562fdd3ef4e..b7a21e11dcdf 100644 --- a/drivers/misc/mic/host/mic_x100.c +++ b/drivers/misc/mic/host/mic_x100.c @@ -549,6 +549,13 @@ struct mic_smpt_ops mic_x100_smpt_ops = { .set = mic_x100_smpt_set, }; +static bool mic_x100_dma_filter(struct dma_chan *chan, void *param) +{ + if (chan->device->dev->parent == (struct device *)param) + return true; + return false; +} + struct mic_hw_ops mic_x100_ops = { .aper_bar = MIC_X100_APER_BAR, .mmio_bar = MIC_X100_MMIO_BAR, @@ -563,6 +570,7 @@ struct mic_hw_ops mic_x100_ops = { .send_firmware_intr = mic_x100_send_firmware_intr, .load_mic_fw = mic_x100_load_firmware, .get_postcode = mic_x100_get_postcode, + .dma_filter = mic_x100_dma_filter, }; struct mic_hw_intr_ops mic_x100_intr_ops = { -- cgit v1.2.3 From 9c3d37c7a1efcb1815ec5cb22f70ca98da24f6b1 Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:24 -0700 Subject: misc: mic: add threaded irq support in card driver Add threaded irq support in mic_request_card_irq which will be used for virtual devices added on mic bus. Reviewed-by: Ashutosh Dixit Reviewed-by: Nikhil Rao Reviewed-by: Sudeep Dutt Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/card/mic_device.c | 23 +++++++++++++---------- drivers/misc/mic/card/mic_device.h | 5 +++-- drivers/misc/mic/card/mic_virtio.c | 7 ++++--- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/drivers/misc/mic/card/mic_device.c b/drivers/misc/mic/card/mic_device.c index d0980ff96833..83819eee553b 100644 --- a/drivers/misc/mic/card/mic_device.c +++ b/drivers/misc/mic/card/mic_device.c @@ -83,8 +83,8 @@ static int mic_shutdown_init(void) int shutdown_db; shutdown_db = mic_next_card_db(); - shutdown_cookie = mic_request_card_irq(mic_shutdown_isr, - "Shutdown", mdrv, shutdown_db); + shutdown_cookie = mic_request_card_irq(mic_shutdown_isr, NULL, + "Shutdown", mdrv, shutdown_db); if (IS_ERR(shutdown_cookie)) rc = PTR_ERR(shutdown_cookie); else @@ -136,7 +136,8 @@ static void mic_dp_uninit(void) /** * mic_request_card_irq - request an irq. * - * @func: The callback function that handles the interrupt. + * @handler: interrupt handler passed to request_threaded_irq. + * @thread_fn: thread fn. passed to request_threaded_irq. * @name: The ASCII name of the callee requesting the irq. * @data: private data that is returned back when calling the * function handler. @@ -149,17 +150,19 @@ static void mic_dp_uninit(void) * error code. * */ -struct mic_irq *mic_request_card_irq(irqreturn_t (*func)(int irq, void *data), - const char *name, void *data, int index) +struct mic_irq * +mic_request_card_irq(irq_handler_t handler, + irq_handler_t thread_fn, const char *name, + void *data, int index) { int rc = 0; unsigned long cookie; struct mic_driver *mdrv = g_drv; - rc = request_irq(mic_db_to_irq(mdrv, index), func, - 0, name, data); + rc = request_threaded_irq(mic_db_to_irq(mdrv, index), handler, + thread_fn, 0, name, data); if (rc) { - dev_err(mdrv->dev, "request_irq failed rc = %d\n", rc); + dev_err(mdrv->dev, "request_threaded_irq failed rc = %d\n", rc); goto err; } mdrv->irq_info.irq_usage_count[index]++; @@ -172,9 +175,9 @@ err: /** * mic_free_card_irq - free irq. * - * @cookie: cookie obtained during a successful call to mic_request_irq + * @cookie: cookie obtained during a successful call to mic_request_threaded_irq * @data: private data specified by the calling function during the - * mic_request_irq + * mic_request_threaded_irq * * returns: none. */ diff --git a/drivers/misc/mic/card/mic_device.h b/drivers/misc/mic/card/mic_device.h index 306f502be95e..e12a0c2ddb3d 100644 --- a/drivers/misc/mic/card/mic_device.h +++ b/drivers/misc/mic/card/mic_device.h @@ -30,6 +30,7 @@ #include #include #include +#include /** * struct mic_intr_info - Contains h/w specific interrupt sources info @@ -116,8 +117,8 @@ mic_mmio_write(struct mic_mw *mw, u32 val, u32 offset) int mic_driver_init(struct mic_driver *mdrv); void mic_driver_uninit(struct mic_driver *mdrv); int mic_next_card_db(void); -struct mic_irq *mic_request_card_irq(irqreturn_t (*func)(int irq, void *data), - const char *name, void *data, int intr_src); +struct mic_irq *mic_request_card_irq(irq_handler_t handler, + irq_handler_t thread_fn, const char *name, void *data, int intr_src); void mic_free_card_irq(struct mic_irq *cookie, void *data); u32 mic_read_spad(struct mic_device *mdev, unsigned int idx); void mic_send_intr(struct mic_device *mdev, int doorbell); diff --git a/drivers/misc/mic/card/mic_virtio.c b/drivers/misc/mic/card/mic_virtio.c index 653799b96bfa..f14b60080c21 100644 --- a/drivers/misc/mic/card/mic_virtio.c +++ b/drivers/misc/mic/card/mic_virtio.c @@ -417,7 +417,7 @@ static int mic_add_device(struct mic_device_desc __iomem *d, virtio_db = mic_next_card_db(); mvdev->virtio_cookie = mic_request_card_irq(mic_virtio_intr_handler, - "virtio intr", mvdev, virtio_db); + NULL, "virtio intr", mvdev, virtio_db); if (IS_ERR(mvdev->virtio_cookie)) { ret = PTR_ERR(mvdev->virtio_cookie); goto kfree; @@ -606,8 +606,9 @@ int mic_devices_init(struct mic_driver *mdrv) mic_scan_devices(mdrv, !REMOVE_DEVICES); config_db = mic_next_card_db(); - virtio_config_cookie = mic_request_card_irq(mic_extint_handler, - "virtio_config_intr", mdrv, config_db); + virtio_config_cookie = mic_request_card_irq(mic_extint_handler, NULL, + "virtio_config_intr", mdrv, + config_db); if (IS_ERR(virtio_config_cookie)) { rc = PTR_ERR(virtio_config_cookie); goto exit; -- cgit v1.2.3 From a93a5244ed7bd3c5f7b51ccb08a14655820e38c3 Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:25 -0700 Subject: misc: mic: add dma support in card driver This patch adds a dma device on the mic virtual bus Reviewed-by: Nikhil Rao Signed-off-by: Sudeep Dutt Signed-off-by: Siva Yerramreddy Signed-off-by: Ashutosh Dixit Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/Kconfig | 2 +- drivers/misc/mic/card/mic_device.h | 8 ++++-- drivers/misc/mic/card/mic_x100.c | 55 +++++++++++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/drivers/misc/mic/Kconfig b/drivers/misc/mic/Kconfig index bf76313dab16..cc4eef040c14 100644 --- a/drivers/misc/mic/Kconfig +++ b/drivers/misc/mic/Kconfig @@ -39,7 +39,7 @@ comment "Intel MIC Card Driver" config INTEL_MIC_CARD tristate "Intel MIC Card Driver" - depends on 64BIT && X86 + depends on 64BIT && X86 && INTEL_MIC_BUS select VIRTIO help This enables card driver support for the Intel Many Integrated diff --git a/drivers/misc/mic/card/mic_device.h b/drivers/misc/mic/card/mic_device.h index e12a0c2ddb3d..844be8fc9b22 100644 --- a/drivers/misc/mic/card/mic_device.h +++ b/drivers/misc/mic/card/mic_device.h @@ -31,6 +31,7 @@ #include #include #include +#include /** * struct mic_intr_info - Contains h/w specific interrupt sources info @@ -71,6 +72,7 @@ struct mic_device { * @hotplug_work: Hot plug work for adding/removing virtio devices. * @irq_info: The OS specific irq information * @intr_info: H/W specific interrupt information. + * @dma_mbdev: dma device on the MIC virtual bus. */ struct mic_driver { char name[20]; @@ -81,6 +83,7 @@ struct mic_driver { struct work_struct hotplug_work; struct mic_irq_info irq_info; struct mic_intr_info intr_info; + struct mbus_device *dma_mbdev; }; /** @@ -117,8 +120,9 @@ mic_mmio_write(struct mic_mw *mw, u32 val, u32 offset) int mic_driver_init(struct mic_driver *mdrv); void mic_driver_uninit(struct mic_driver *mdrv); int mic_next_card_db(void); -struct mic_irq *mic_request_card_irq(irq_handler_t handler, - irq_handler_t thread_fn, const char *name, void *data, int intr_src); +struct mic_irq * +mic_request_card_irq(irq_handler_t handler, irq_handler_t thread_fn, + const char *name, void *data, int intr_src); void mic_free_card_irq(struct mic_irq *cookie, void *data); u32 mic_read_spad(struct mic_device *mdev, unsigned int idx); void mic_send_intr(struct mic_device *mdev, int doorbell); diff --git a/drivers/misc/mic/card/mic_x100.c b/drivers/misc/mic/card/mic_x100.c index 2868945c9a4d..55c9465bd260 100644 --- a/drivers/misc/mic/card/mic_x100.c +++ b/drivers/misc/mic/card/mic_x100.c @@ -148,6 +148,47 @@ void mic_card_unmap(struct mic_device *mdev, void __iomem *addr) iounmap(addr); } +static inline struct mic_driver *mbdev_to_mdrv(struct mbus_device *mbdev) +{ + return dev_get_drvdata(mbdev->dev.parent); +} + +static struct mic_irq * +_mic_request_threaded_irq(struct mbus_device *mbdev, + irq_handler_t handler, irq_handler_t thread_fn, + const char *name, void *data, int intr_src) +{ + int rc = 0; + unsigned int irq = intr_src; + unsigned long cookie = irq; + + rc = request_threaded_irq(irq, handler, thread_fn, 0, name, data); + if (rc) { + dev_err(mbdev_to_mdrv(mbdev)->dev, + "request_threaded_irq failed rc = %d\n", rc); + return ERR_PTR(rc); + } + return (struct mic_irq *)cookie; +} + +static void _mic_free_irq(struct mbus_device *mbdev, + struct mic_irq *cookie, void *data) +{ + unsigned long irq = (unsigned long)cookie; + free_irq(irq, data); +} + +static void _mic_ack_interrupt(struct mbus_device *mbdev, int num) +{ + mic_ack_interrupt(&mbdev_to_mdrv(mbdev)->mdev); +} + +static struct mbus_hw_ops mbus_hw_ops = { + .request_threaded_irq = _mic_request_threaded_irq, + .free_irq = _mic_free_irq, + .ack_interrupt = _mic_ack_interrupt, +}; + static int __init mic_probe(struct platform_device *pdev) { struct mic_driver *mdrv = &g_drv; @@ -166,13 +207,24 @@ static int __init mic_probe(struct platform_device *pdev) goto done; } mic_hw_intr_init(mdrv); + platform_set_drvdata(pdev, mdrv); + mdrv->dma_mbdev = mbus_register_device(mdrv->dev, MBUS_DEV_DMA_MIC, + NULL, &mbus_hw_ops, + mdrv->mdev.mmio.va); + if (IS_ERR(mdrv->dma_mbdev)) { + rc = PTR_ERR(mdrv->dma_mbdev); + dev_err(&pdev->dev, "mbus_add_device failed rc %d\n", rc); + goto iounmap; + } rc = mic_driver_init(mdrv); if (rc) { dev_err(&pdev->dev, "mic_driver_init failed rc %d\n", rc); - goto iounmap; + goto remove_dma; } done: return rc; +remove_dma: + mbus_unregister_device(mdrv->dma_mbdev); iounmap: iounmap(mdev->mmio.va); return rc; @@ -184,6 +236,7 @@ static int mic_remove(struct platform_device *pdev) struct mic_device *mdev = &mdrv->mdev; mic_driver_uninit(mdrv); + mbus_unregister_device(mdrv->dma_mbdev); iounmap(mdev->mmio.va); return 0; } -- cgit v1.2.3 From a8438814d3b899e04e808e45585b40931517244f Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:26 -0700 Subject: misc: mic: add support for loading/unloading dma driver modprobe dma driver upon start and remove it upon unload. Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- Documentation/mic/mpssd/mpss | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Documentation/mic/mpssd/mpss b/Documentation/mic/mpssd/mpss index 3136c68dad0b..cacbdb0aefb9 100755 --- a/Documentation/mic/mpssd/mpss +++ b/Documentation/mic/mpssd/mpss @@ -48,18 +48,18 @@ start() fi echo -e $"Starting MPSS Stack" - echo -e $"Loading MIC_HOST Module" + echo -e $"Loading MIC_X100_DMA & MIC_HOST Modules" - # Ensure the driver is loaded - if [ ! -d "$sysfs" ]; then - modprobe mic_host + for f in "mic_host" "mic_x100_dma" + do + modprobe $f RETVAL=$? if [ $RETVAL -ne 0 ]; then failure echo return $RETVAL fi - fi + done # Start the daemon echo -n $"Starting MPSSD " @@ -170,8 +170,8 @@ unload() stop sleep 5 - echo -n $"Removing MIC_HOST Module: " - modprobe -r mic_host + echo -n $"Removing MIC_HOST & MIC_X100_DMA Modules: " + modprobe -r mic_host mic_x100_dma RETVAL=$? [ $RETVAL -ne 0 ] && failure || success echo -- cgit v1.2.3 From 95b4ecbf759ae8ecf40462ed5e6a08023166a05c Mon Sep 17 00:00:00 2001 From: Siva Yerramreddy Date: Fri, 11 Jul 2014 14:04:21 -0700 Subject: dma: MIC X100 DMA Driver This patch implements DMA Engine API for DMA controller on MIC X100 Coprocessors. DMA h/w is shared between host and card s/w. Channels 0 to 3 are used by host and 4 to 7 are used by card. Since the DMA device doesn't show up as PCIe device, a virtual bus called mic bus is created and virtual devices are added on that bus to follow device model. Allowed dma transfer directions are host to card, card to host and card to card. Reviewed-by: Ashutosh Dixit Reviewed-by: Nikhil Rao Reviewed-by: Sudeep Dutt Signed-off-by: Siva Yerramreddy Signed-off-by: Greg Kroah-Hartman --- drivers/dma/Kconfig | 19 ++ drivers/dma/Makefile | 1 + drivers/dma/mic_x100_dma.c | 774 +++++++++++++++++++++++++++++++++++++++++++++ drivers/dma/mic_x100_dma.h | 286 +++++++++++++++++ 4 files changed, 1080 insertions(+) create mode 100644 drivers/dma/mic_x100_dma.c create mode 100644 drivers/dma/mic_x100_dma.h diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 1eca7b9760e6..7c8b8c41f4f4 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -33,6 +33,25 @@ if DMADEVICES comment "DMA Devices" +config INTEL_MIC_X100_DMA + tristate "Intel MIC X100 DMA Driver" + depends on 64BIT && X86 && INTEL_MIC_BUS + select DMAENGINE + default N + help + This enables DMA support for the Intel Many Integrated Core + (MIC) family of PCIe form factor coprocessor X100 devices that + run a 64 bit Linux OS. This driver will be used by both MIC + host and card drivers. + + If you are building host kernel with a MIC device or a card + kernel for a MIC device, then say M (recommended) or Y, else + say N. If unsure say N. + + More information about the Intel MIC family as well as the Linux + OS and tools for MIC to use with this driver are available from + . + config INTEL_MID_DMAC tristate "Intel MID DMA support for Peripheral DMA controllers" depends on PCI && X86 diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index c779e1eb2db2..bd9e7fa928bd 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -47,3 +47,4 @@ obj-$(CONFIG_MOXART_DMA) += moxart-dma.o obj-$(CONFIG_FSL_EDMA) += fsl-edma.o obj-$(CONFIG_QCOM_BAM_DMA) += qcom_bam_dma.o obj-y += xilinx/ +obj-$(CONFIG_INTEL_MIC_X100_DMA) += mic_x100_dma.o diff --git a/drivers/dma/mic_x100_dma.c b/drivers/dma/mic_x100_dma.c new file mode 100644 index 000000000000..6de2e677be04 --- /dev/null +++ b/drivers/dma/mic_x100_dma.c @@ -0,0 +1,774 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC X100 DMA Driver. + * + * Adapted from IOAT dma driver. + */ +#include +#include +#include + +#include "mic_x100_dma.h" + +#define MIC_DMA_MAX_XFER_SIZE_CARD (1 * 1024 * 1024 -\ + MIC_DMA_ALIGN_BYTES) +#define MIC_DMA_MAX_XFER_SIZE_HOST (1 * 1024 * 1024 >> 1) +#define MIC_DMA_DESC_TYPE_SHIFT 60 +#define MIC_DMA_MEMCPY_LEN_SHIFT 46 +#define MIC_DMA_STAT_INTR_SHIFT 59 + +/* high-water mark for pushing dma descriptors */ +static int mic_dma_pending_level = 4; + +/* Status descriptor is used to write a 64 bit value to a memory location */ +enum mic_dma_desc_format_type { + MIC_DMA_MEMCPY = 1, + MIC_DMA_STATUS, +}; + +static inline u32 mic_dma_hw_ring_inc(u32 val) +{ + return (val + 1) % MIC_DMA_DESC_RX_SIZE; +} + +static inline u32 mic_dma_hw_ring_dec(u32 val) +{ + return val ? val - 1 : MIC_DMA_DESC_RX_SIZE - 1; +} + +static inline void mic_dma_hw_ring_inc_head(struct mic_dma_chan *ch) +{ + ch->head = mic_dma_hw_ring_inc(ch->head); +} + +/* Prepare a memcpy desc */ +static inline void mic_dma_memcpy_desc(struct mic_dma_desc *desc, + dma_addr_t src_phys, dma_addr_t dst_phys, u64 size) +{ + u64 qw0, qw1; + + qw0 = src_phys; + qw0 |= (size >> MIC_DMA_ALIGN_SHIFT) << MIC_DMA_MEMCPY_LEN_SHIFT; + qw1 = MIC_DMA_MEMCPY; + qw1 <<= MIC_DMA_DESC_TYPE_SHIFT; + qw1 |= dst_phys; + desc->qw0 = qw0; + desc->qw1 = qw1; +} + +/* Prepare a status desc. with @data to be written at @dst_phys */ +static inline void mic_dma_prep_status_desc(struct mic_dma_desc *desc, u64 data, + dma_addr_t dst_phys, bool generate_intr) +{ + u64 qw0, qw1; + + qw0 = data; + qw1 = (u64) MIC_DMA_STATUS << MIC_DMA_DESC_TYPE_SHIFT | dst_phys; + if (generate_intr) + qw1 |= (1ULL << MIC_DMA_STAT_INTR_SHIFT); + desc->qw0 = qw0; + desc->qw1 = qw1; +} + +static void mic_dma_cleanup(struct mic_dma_chan *ch) +{ + struct dma_async_tx_descriptor *tx; + u32 tail; + u32 last_tail; + + spin_lock(&ch->cleanup_lock); + tail = mic_dma_read_cmp_cnt(ch); + /* + * This is the barrier pair for smp_wmb() in fn. + * mic_dma_tx_submit_unlock. It's required so that we read the + * updated cookie value from tx->cookie. + */ + smp_rmb(); + for (last_tail = ch->last_tail; tail != last_tail;) { + tx = &ch->tx_array[last_tail]; + if (tx->cookie) { + dma_cookie_complete(tx); + if (tx->callback) { + tx->callback(tx->callback_param); + tx->callback = NULL; + } + } + last_tail = mic_dma_hw_ring_inc(last_tail); + } + /* finish all completion callbacks before incrementing tail */ + smp_mb(); + ch->last_tail = last_tail; + spin_unlock(&ch->cleanup_lock); +} + +static u32 mic_dma_ring_count(u32 head, u32 tail) +{ + u32 count; + + if (head >= tail) + count = (tail - 0) + (MIC_DMA_DESC_RX_SIZE - head); + else + count = tail - head; + return count - 1; +} + +/* Returns the num. of free descriptors on success, -ENOMEM on failure */ +static int mic_dma_avail_desc_ring_space(struct mic_dma_chan *ch, int required) +{ + struct device *dev = mic_dma_ch_to_device(ch); + u32 count; + + count = mic_dma_ring_count(ch->head, ch->last_tail); + if (count < required) { + mic_dma_cleanup(ch); + count = mic_dma_ring_count(ch->head, ch->last_tail); + } + + if (count < required) { + dev_dbg(dev, "Not enough desc space"); + dev_dbg(dev, "%s %d required=%u, avail=%u\n", + __func__, __LINE__, required, count); + return -ENOMEM; + } else { + return count; + } +} + +/* Program memcpy descriptors into the descriptor ring and update s/w head ptr*/ +static int mic_dma_prog_memcpy_desc(struct mic_dma_chan *ch, dma_addr_t src, + dma_addr_t dst, size_t len) +{ + size_t current_transfer_len; + size_t max_xfer_size = to_mic_dma_dev(ch)->max_xfer_size; + /* 3 is added to make sure we have enough space for status desc */ + int num_desc = len / max_xfer_size + 3; + int ret; + + if (len % max_xfer_size) + num_desc++; + + ret = mic_dma_avail_desc_ring_space(ch, num_desc); + if (ret < 0) + return ret; + do { + current_transfer_len = min(len, max_xfer_size); + mic_dma_memcpy_desc(&ch->desc_ring[ch->head], + src, dst, current_transfer_len); + mic_dma_hw_ring_inc_head(ch); + len -= current_transfer_len; + dst = dst + current_transfer_len; + src = src + current_transfer_len; + } while (len > 0); + return 0; +} + +/* It's a h/w quirk and h/w needs 2 status descriptors for every status desc */ +static void mic_dma_prog_intr(struct mic_dma_chan *ch) +{ + mic_dma_prep_status_desc(&ch->desc_ring[ch->head], 0, + ch->status_dest_micpa, false); + mic_dma_hw_ring_inc_head(ch); + mic_dma_prep_status_desc(&ch->desc_ring[ch->head], 0, + ch->status_dest_micpa, true); + mic_dma_hw_ring_inc_head(ch); +} + +/* Wrapper function to program memcpy descriptors/status descriptors */ +static int mic_dma_do_dma(struct mic_dma_chan *ch, int flags, dma_addr_t src, + dma_addr_t dst, size_t len) +{ + if (-ENOMEM == mic_dma_prog_memcpy_desc(ch, src, dst, len)) + return -ENOMEM; + /* Above mic_dma_prog_memcpy_desc() makes sure we have enough space */ + if (flags & DMA_PREP_FENCE) { + mic_dma_prep_status_desc(&ch->desc_ring[ch->head], 0, + ch->status_dest_micpa, false); + mic_dma_hw_ring_inc_head(ch); + } + + if (flags & DMA_PREP_INTERRUPT) + mic_dma_prog_intr(ch); + + return 0; +} + +static inline void mic_dma_issue_pending(struct dma_chan *ch) +{ + struct mic_dma_chan *mic_ch = to_mic_dma_chan(ch); + + spin_lock(&mic_ch->issue_lock); + /* + * Write to head triggers h/w to act on the descriptors. + * On MIC, writing the same head value twice causes + * a h/w error. On second write, h/w assumes we filled + * the entire ring & overwrote some of the descriptors. + */ + if (mic_ch->issued == mic_ch->submitted) + goto out; + mic_ch->issued = mic_ch->submitted; + /* + * make descriptor updates visible before advancing head, + * this is purposefully not smp_wmb() since we are also + * publishing the descriptor updates to a dma device + */ + wmb(); + mic_dma_write_reg(mic_ch, MIC_DMA_REG_DHPR, mic_ch->issued); +out: + spin_unlock(&mic_ch->issue_lock); +} + +static inline void mic_dma_update_pending(struct mic_dma_chan *ch) +{ + if (mic_dma_ring_count(ch->issued, ch->submitted) + > mic_dma_pending_level) + mic_dma_issue_pending(&ch->api_ch); +} + +static dma_cookie_t mic_dma_tx_submit_unlock(struct dma_async_tx_descriptor *tx) +{ + struct mic_dma_chan *mic_ch = to_mic_dma_chan(tx->chan); + dma_cookie_t cookie; + + dma_cookie_assign(tx); + cookie = tx->cookie; + /* + * We need an smp write barrier here because another CPU might see + * an update to submitted and update h/w head even before we + * assigned a cookie to this tx. + */ + smp_wmb(); + mic_ch->submitted = mic_ch->head; + spin_unlock(&mic_ch->prep_lock); + mic_dma_update_pending(mic_ch); + return cookie; +} + +static inline struct dma_async_tx_descriptor * +allocate_tx(struct mic_dma_chan *ch) +{ + u32 idx = mic_dma_hw_ring_dec(ch->head); + struct dma_async_tx_descriptor *tx = &ch->tx_array[idx]; + + dma_async_tx_descriptor_init(tx, &ch->api_ch); + tx->tx_submit = mic_dma_tx_submit_unlock; + return tx; +} + +/* + * Prepare a memcpy descriptor to be added to the ring. + * Note that the temporary descriptor adds an extra overhead of copying the + * descriptor to ring. So, we copy directly to the descriptor ring + */ +static struct dma_async_tx_descriptor * +mic_dma_prep_memcpy_lock(struct dma_chan *ch, dma_addr_t dma_dest, + dma_addr_t dma_src, size_t len, unsigned long flags) +{ + struct mic_dma_chan *mic_ch = to_mic_dma_chan(ch); + struct device *dev = mic_dma_ch_to_device(mic_ch); + int result; + + if (!len && !flags) + return NULL; + + spin_lock(&mic_ch->prep_lock); + result = mic_dma_do_dma(mic_ch, flags, dma_src, dma_dest, len); + if (result >= 0) + return allocate_tx(mic_ch); + dev_err(dev, "Error enqueueing dma, error=%d\n", result); + spin_unlock(&mic_ch->prep_lock); + return NULL; +} + +static struct dma_async_tx_descriptor * +mic_dma_prep_interrupt_lock(struct dma_chan *ch, unsigned long flags) +{ + struct mic_dma_chan *mic_ch = to_mic_dma_chan(ch); + int ret; + + spin_lock(&mic_ch->prep_lock); + ret = mic_dma_do_dma(mic_ch, flags, 0, 0, 0); + if (!ret) + return allocate_tx(mic_ch); + spin_unlock(&mic_ch->prep_lock); + return NULL; +} + +/* Return the status of the transaction */ +static enum dma_status +mic_dma_tx_status(struct dma_chan *ch, dma_cookie_t cookie, + struct dma_tx_state *txstate) +{ + struct mic_dma_chan *mic_ch = to_mic_dma_chan(ch); + + if (DMA_COMPLETE != dma_cookie_status(ch, cookie, txstate)) + mic_dma_cleanup(mic_ch); + + return dma_cookie_status(ch, cookie, txstate); +} + +static irqreturn_t mic_dma_thread_fn(int irq, void *data) +{ + mic_dma_cleanup((struct mic_dma_chan *)data); + return IRQ_HANDLED; +} + +static irqreturn_t mic_dma_intr_handler(int irq, void *data) +{ + struct mic_dma_chan *ch = ((struct mic_dma_chan *)data); + + mic_dma_ack_interrupt(ch); + return IRQ_WAKE_THREAD; +} + +static int mic_dma_alloc_desc_ring(struct mic_dma_chan *ch) +{ + u64 desc_ring_size = MIC_DMA_DESC_RX_SIZE * sizeof(*ch->desc_ring); + struct device *dev = &to_mbus_device(ch)->dev; + + desc_ring_size = ALIGN(desc_ring_size, MIC_DMA_ALIGN_BYTES); + ch->desc_ring = kzalloc(desc_ring_size, GFP_KERNEL); + + if (!ch->desc_ring) + return -ENOMEM; + + ch->desc_ring_micpa = dma_map_single(dev, ch->desc_ring, + desc_ring_size, DMA_BIDIRECTIONAL); + if (dma_mapping_error(dev, ch->desc_ring_micpa)) + goto map_error; + + ch->tx_array = vzalloc(MIC_DMA_DESC_RX_SIZE * sizeof(*ch->tx_array)); + if (!ch->tx_array) + goto tx_error; + return 0; +tx_error: + dma_unmap_single(dev, ch->desc_ring_micpa, desc_ring_size, + DMA_BIDIRECTIONAL); +map_error: + kfree(ch->desc_ring); + return -ENOMEM; +} + +static void mic_dma_free_desc_ring(struct mic_dma_chan *ch) +{ + u64 desc_ring_size = MIC_DMA_DESC_RX_SIZE * sizeof(*ch->desc_ring); + + vfree(ch->tx_array); + desc_ring_size = ALIGN(desc_ring_size, MIC_DMA_ALIGN_BYTES); + dma_unmap_single(&to_mbus_device(ch)->dev, ch->desc_ring_micpa, + desc_ring_size, DMA_BIDIRECTIONAL); + kfree(ch->desc_ring); + ch->desc_ring = NULL; +} + +static void mic_dma_free_status_dest(struct mic_dma_chan *ch) +{ + dma_unmap_single(&to_mbus_device(ch)->dev, ch->status_dest_micpa, + L1_CACHE_BYTES, DMA_BIDIRECTIONAL); + kfree(ch->status_dest); +} + +static int mic_dma_alloc_status_dest(struct mic_dma_chan *ch) +{ + struct device *dev = &to_mbus_device(ch)->dev; + + ch->status_dest = kzalloc(L1_CACHE_BYTES, GFP_KERNEL); + if (!ch->status_dest) + return -ENOMEM; + ch->status_dest_micpa = dma_map_single(dev, ch->status_dest, + L1_CACHE_BYTES, DMA_BIDIRECTIONAL); + if (dma_mapping_error(dev, ch->status_dest_micpa)) { + kfree(ch->status_dest); + ch->status_dest = NULL; + return -ENOMEM; + } + return 0; +} + +static int mic_dma_check_chan(struct mic_dma_chan *ch) +{ + if (mic_dma_read_reg(ch, MIC_DMA_REG_DCHERR) || + mic_dma_read_reg(ch, MIC_DMA_REG_DSTAT) & MIC_DMA_CHAN_QUIESCE) { + mic_dma_disable_chan(ch); + mic_dma_chan_mask_intr(ch); + dev_err(mic_dma_ch_to_device(ch), + "%s %d error setting up mic dma chan %d\n", + __func__, __LINE__, ch->ch_num); + return -EBUSY; + } + return 0; +} + +static int mic_dma_chan_setup(struct mic_dma_chan *ch) +{ + if (MIC_DMA_CHAN_MIC == ch->owner) + mic_dma_chan_set_owner(ch); + mic_dma_disable_chan(ch); + mic_dma_chan_mask_intr(ch); + mic_dma_write_reg(ch, MIC_DMA_REG_DCHERRMSK, 0); + mic_dma_chan_set_desc_ring(ch); + ch->last_tail = mic_dma_read_reg(ch, MIC_DMA_REG_DTPR); + ch->head = ch->last_tail; + ch->issued = 0; + mic_dma_chan_unmask_intr(ch); + mic_dma_enable_chan(ch); + return mic_dma_check_chan(ch); +} + +static void mic_dma_chan_destroy(struct mic_dma_chan *ch) +{ + mic_dma_disable_chan(ch); + mic_dma_chan_mask_intr(ch); +} + +static void mic_dma_unregister_dma_device(struct mic_dma_device *mic_dma_dev) +{ + dma_async_device_unregister(&mic_dma_dev->dma_dev); +} + +static int mic_dma_setup_irq(struct mic_dma_chan *ch) +{ + ch->cookie = + to_mbus_hw_ops(ch)->request_threaded_irq(to_mbus_device(ch), + mic_dma_intr_handler, mic_dma_thread_fn, + "mic dma_channel", ch, ch->ch_num); + if (IS_ERR(ch->cookie)) + return IS_ERR(ch->cookie); + return 0; +} + +static inline void mic_dma_free_irq(struct mic_dma_chan *ch) +{ + to_mbus_hw_ops(ch)->free_irq(to_mbus_device(ch), ch->cookie, ch); +} + +static int mic_dma_chan_init(struct mic_dma_chan *ch) +{ + int ret = mic_dma_alloc_desc_ring(ch); + + if (ret) + goto ring_error; + ret = mic_dma_alloc_status_dest(ch); + if (ret) + goto status_error; + ret = mic_dma_chan_setup(ch); + if (ret) + goto chan_error; + return ret; +chan_error: + mic_dma_free_status_dest(ch); +status_error: + mic_dma_free_desc_ring(ch); +ring_error: + return ret; +} + +static int mic_dma_drain_chan(struct mic_dma_chan *ch) +{ + struct dma_async_tx_descriptor *tx; + int err = 0; + dma_cookie_t cookie; + + tx = mic_dma_prep_memcpy_lock(&ch->api_ch, 0, 0, 0, DMA_PREP_FENCE); + if (!tx) { + err = -ENOMEM; + goto error; + } + + cookie = tx->tx_submit(tx); + if (dma_submit_error(cookie)) + err = -ENOMEM; + else + err = dma_sync_wait(&ch->api_ch, cookie); + if (err) { + dev_err(mic_dma_ch_to_device(ch), "%s %d TO chan 0x%x\n", + __func__, __LINE__, ch->ch_num); + err = -EIO; + } +error: + mic_dma_cleanup(ch); + return err; +} + +static inline void mic_dma_chan_uninit(struct mic_dma_chan *ch) +{ + mic_dma_chan_destroy(ch); + mic_dma_cleanup(ch); + mic_dma_free_status_dest(ch); + mic_dma_free_desc_ring(ch); +} + +static int mic_dma_init(struct mic_dma_device *mic_dma_dev, + enum mic_dma_chan_owner owner) +{ + int i, first_chan = mic_dma_dev->start_ch; + struct mic_dma_chan *ch; + int ret; + + for (i = first_chan; i < first_chan + MIC_DMA_NUM_CHAN; i++) { + unsigned long data; + ch = &mic_dma_dev->mic_ch[i]; + data = (unsigned long)ch; + ch->ch_num = i; + ch->owner = owner; + spin_lock_init(&ch->cleanup_lock); + spin_lock_init(&ch->prep_lock); + spin_lock_init(&ch->issue_lock); + ret = mic_dma_setup_irq(ch); + if (ret) + goto error; + } + return 0; +error: + for (i = i - 1; i >= first_chan; i--) + mic_dma_free_irq(ch); + return ret; +} + +static void mic_dma_uninit(struct mic_dma_device *mic_dma_dev) +{ + int i, first_chan = mic_dma_dev->start_ch; + struct mic_dma_chan *ch; + + for (i = first_chan; i < first_chan + MIC_DMA_NUM_CHAN; i++) { + ch = &mic_dma_dev->mic_ch[i]; + mic_dma_free_irq(ch); + } +} + +static int mic_dma_alloc_chan_resources(struct dma_chan *ch) +{ + int ret = mic_dma_chan_init(to_mic_dma_chan(ch)); + if (ret) + return ret; + return MIC_DMA_DESC_RX_SIZE; +} + +static void mic_dma_free_chan_resources(struct dma_chan *ch) +{ + struct mic_dma_chan *mic_ch = to_mic_dma_chan(ch); + mic_dma_drain_chan(mic_ch); + mic_dma_chan_uninit(mic_ch); +} + +/* Set the fn. handlers and register the dma device with dma api */ +static int mic_dma_register_dma_device(struct mic_dma_device *mic_dma_dev, + enum mic_dma_chan_owner owner) +{ + int i, first_chan = mic_dma_dev->start_ch; + + dma_cap_zero(mic_dma_dev->dma_dev.cap_mask); + /* + * This dma engine is not capable of host memory to host memory + * transfers + */ + dma_cap_set(DMA_MEMCPY, mic_dma_dev->dma_dev.cap_mask); + + if (MIC_DMA_CHAN_HOST == owner) + dma_cap_set(DMA_PRIVATE, mic_dma_dev->dma_dev.cap_mask); + mic_dma_dev->dma_dev.device_alloc_chan_resources = + mic_dma_alloc_chan_resources; + mic_dma_dev->dma_dev.device_free_chan_resources = + mic_dma_free_chan_resources; + mic_dma_dev->dma_dev.device_tx_status = mic_dma_tx_status; + mic_dma_dev->dma_dev.device_prep_dma_memcpy = mic_dma_prep_memcpy_lock; + mic_dma_dev->dma_dev.device_prep_dma_interrupt = + mic_dma_prep_interrupt_lock; + mic_dma_dev->dma_dev.device_issue_pending = mic_dma_issue_pending; + mic_dma_dev->dma_dev.copy_align = MIC_DMA_ALIGN_SHIFT; + INIT_LIST_HEAD(&mic_dma_dev->dma_dev.channels); + for (i = first_chan; i < first_chan + MIC_DMA_NUM_CHAN; i++) { + mic_dma_dev->mic_ch[i].api_ch.device = &mic_dma_dev->dma_dev; + dma_cookie_init(&mic_dma_dev->mic_ch[i].api_ch); + list_add_tail(&mic_dma_dev->mic_ch[i].api_ch.device_node, + &mic_dma_dev->dma_dev.channels); + } + return dma_async_device_register(&mic_dma_dev->dma_dev); +} + +/* + * Initializes dma channels and registers the dma device with the + * dma engine api. + */ +static struct mic_dma_device *mic_dma_dev_reg(struct mbus_device *mbdev, + enum mic_dma_chan_owner owner) +{ + struct mic_dma_device *mic_dma_dev; + int ret; + struct device *dev = &mbdev->dev; + + mic_dma_dev = kzalloc(sizeof(*mic_dma_dev), GFP_KERNEL); + if (!mic_dma_dev) { + ret = -ENOMEM; + goto alloc_error; + } + mic_dma_dev->mbdev = mbdev; + mic_dma_dev->dma_dev.dev = dev; + mic_dma_dev->mmio = mbdev->mmio_va; + if (MIC_DMA_CHAN_HOST == owner) { + mic_dma_dev->start_ch = 0; + mic_dma_dev->max_xfer_size = MIC_DMA_MAX_XFER_SIZE_HOST; + } else { + mic_dma_dev->start_ch = 4; + mic_dma_dev->max_xfer_size = MIC_DMA_MAX_XFER_SIZE_CARD; + } + ret = mic_dma_init(mic_dma_dev, owner); + if (ret) + goto init_error; + ret = mic_dma_register_dma_device(mic_dma_dev, owner); + if (ret) + goto reg_error; + return mic_dma_dev; +reg_error: + mic_dma_uninit(mic_dma_dev); +init_error: + kfree(mic_dma_dev); + mic_dma_dev = NULL; +alloc_error: + dev_err(dev, "Error at %s %d ret=%d\n", __func__, __LINE__, ret); + return mic_dma_dev; +} + +static void mic_dma_dev_unreg(struct mic_dma_device *mic_dma_dev) +{ + mic_dma_unregister_dma_device(mic_dma_dev); + mic_dma_uninit(mic_dma_dev); + kfree(mic_dma_dev); +} + +/* DEBUGFS CODE */ +static int mic_dma_reg_seq_show(struct seq_file *s, void *pos) +{ + struct mic_dma_device *mic_dma_dev = s->private; + int i, chan_num, first_chan = mic_dma_dev->start_ch; + struct mic_dma_chan *ch; + + seq_printf(s, "SBOX_DCR: %#x\n", + mic_dma_mmio_read(&mic_dma_dev->mic_ch[first_chan], + MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR)); + seq_puts(s, "DMA Channel Registers\n"); + seq_printf(s, "%-10s| %-10s %-10s %-10s %-10s %-10s", + "Channel", "DCAR", "DTPR", "DHPR", "DRAR_HI", "DRAR_LO"); + seq_printf(s, " %-11s %-14s %-10s\n", "DCHERR", "DCHERRMSK", "DSTAT"); + for (i = first_chan; i < first_chan + MIC_DMA_NUM_CHAN; i++) { + ch = &mic_dma_dev->mic_ch[i]; + chan_num = ch->ch_num; + seq_printf(s, "%-10i| %-#10x %-#10x %-#10x %-#10x", + chan_num, + mic_dma_read_reg(ch, MIC_DMA_REG_DCAR), + mic_dma_read_reg(ch, MIC_DMA_REG_DTPR), + mic_dma_read_reg(ch, MIC_DMA_REG_DHPR), + mic_dma_read_reg(ch, MIC_DMA_REG_DRAR_HI)); + seq_printf(s, " %-#10x %-#10x %-#14x %-#10x\n", + mic_dma_read_reg(ch, MIC_DMA_REG_DRAR_LO), + mic_dma_read_reg(ch, MIC_DMA_REG_DCHERR), + mic_dma_read_reg(ch, MIC_DMA_REG_DCHERRMSK), + mic_dma_read_reg(ch, MIC_DMA_REG_DSTAT)); + } + return 0; +} + +static int mic_dma_reg_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, mic_dma_reg_seq_show, inode->i_private); +} + +static int mic_dma_reg_debug_release(struct inode *inode, struct file *file) +{ + return single_release(inode, file); +} + +static const struct file_operations mic_dma_reg_ops = { + .owner = THIS_MODULE, + .open = mic_dma_reg_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = mic_dma_reg_debug_release +}; + +/* Debugfs parent dir */ +static struct dentry *mic_dma_dbg; + +static int mic_dma_driver_probe(struct mbus_device *mbdev) +{ + struct mic_dma_device *mic_dma_dev; + enum mic_dma_chan_owner owner; + + if (MBUS_DEV_DMA_MIC == mbdev->id.device) + owner = MIC_DMA_CHAN_MIC; + else + owner = MIC_DMA_CHAN_HOST; + + mic_dma_dev = mic_dma_dev_reg(mbdev, owner); + dev_set_drvdata(&mbdev->dev, mic_dma_dev); + + if (mic_dma_dbg) { + mic_dma_dev->dbg_dir = debugfs_create_dir(dev_name(&mbdev->dev), + mic_dma_dbg); + if (mic_dma_dev->dbg_dir) + debugfs_create_file("mic_dma_reg", 0444, + mic_dma_dev->dbg_dir, mic_dma_dev, + &mic_dma_reg_ops); + } + return 0; +} + +static void mic_dma_driver_remove(struct mbus_device *mbdev) +{ + struct mic_dma_device *mic_dma_dev; + + mic_dma_dev = dev_get_drvdata(&mbdev->dev); + debugfs_remove_recursive(mic_dma_dev->dbg_dir); + mic_dma_dev_unreg(mic_dma_dev); +} + +static struct mbus_device_id id_table[] = { + {MBUS_DEV_DMA_MIC, MBUS_DEV_ANY_ID}, + {MBUS_DEV_DMA_HOST, MBUS_DEV_ANY_ID}, + {0}, +}; + +static struct mbus_driver mic_dma_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = mic_dma_driver_probe, + .remove = mic_dma_driver_remove, +}; + +static int __init mic_x100_dma_init(void) +{ + int rc = mbus_register_driver(&mic_dma_driver); + if (rc) + return rc; + mic_dma_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL); + return 0; +} + +static void __exit mic_x100_dma_exit(void) +{ + debugfs_remove_recursive(mic_dma_dbg); + mbus_unregister_driver(&mic_dma_driver); +} + +module_init(mic_x100_dma_init); +module_exit(mic_x100_dma_exit); + +MODULE_DEVICE_TABLE(mbus, id_table); +MODULE_AUTHOR("Intel Corporation"); +MODULE_DESCRIPTION("Intel(R) MIC X100 DMA Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/dma/mic_x100_dma.h b/drivers/dma/mic_x100_dma.h new file mode 100644 index 000000000000..f663b0bdd11d --- /dev/null +++ b/drivers/dma/mic_x100_dma.h @@ -0,0 +1,286 @@ +/* + * Intel MIC Platform Software Stack (MPSS) + * + * Copyright(c) 2014 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * The full GNU General Public License is included in this distribution in + * the file called "COPYING". + * + * Intel MIC X100 DMA Driver. + * + * Adapted from IOAT dma driver. + */ +#ifndef _MIC_X100_DMA_H_ +#define _MIC_X100_DMA_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "dmaengine.h" + +/* + * MIC has a total of 8 dma channels. + * Four channels are assigned for host SW use & the remaining for MIC SW. + * MIC DMA transfer size & addresses need to be 64 byte aligned. + */ +#define MIC_DMA_MAX_NUM_CHAN 8 +#define MIC_DMA_NUM_CHAN 4 +#define MIC_DMA_ALIGN_SHIFT 6 +#define MIC_DMA_ALIGN_BYTES (1 << MIC_DMA_ALIGN_SHIFT) +#define MIC_DMA_DESC_RX_SIZE (128 * 1024 - 4) + +/* + * Register descriptions + * All the registers are 32 bit registers. + * DCR is a global register and all others are per-channel. + * DCR - bits 0, 2, 4, 6, 8, 10, 12, 14 - enable bits for channels 0 to 7 + * bits 1, 3, 5, 7, 9, 11, 13, 15 - owner bits for channels 0 to 7 + * DCAR - bit 24 & 25 interrupt masks for mic owned & host owned channels + * DHPR - head of the descriptor ring updated by s/w + * DTPR - tail of the descriptor ring updated by h/w + * DRAR_LO - lower 32 bits of descriptor ring's mic address + * DRAR_HI - 3:0 - remaining 4 bits of descriptor ring's mic address + * 20:4 descriptor ring size + * 25:21 mic smpt entry number + * DSTAT - 16:0 h/w completion count; 31:28 dma engine status + * DCHERR - this register is non-zero on error + * DCHERRMSK - interrupt mask register + */ +#define MIC_DMA_HW_CMP_CNT_MASK 0x1ffff +#define MIC_DMA_CHAN_QUIESCE 0x20000000 +#define MIC_DMA_SBOX_BASE 0x00010000 +#define MIC_DMA_SBOX_DCR 0x0000A280 +#define MIC_DMA_SBOX_CH_BASE 0x0001A000 +#define MIC_DMA_SBOX_CHAN_OFF 0x40 +#define MIC_DMA_SBOX_DCAR_IM0 (0x1 << 24) +#define MIC_DMA_SBOX_DCAR_IM1 (0x1 << 25) +#define MIC_DMA_SBOX_DRARHI_SYS_MASK (0x1 << 26) +#define MIC_DMA_REG_DCAR 0 +#define MIC_DMA_REG_DHPR 4 +#define MIC_DMA_REG_DTPR 8 +#define MIC_DMA_REG_DRAR_LO 20 +#define MIC_DMA_REG_DRAR_HI 24 +#define MIC_DMA_REG_DSTAT 32 +#define MIC_DMA_REG_DCHERR 44 +#define MIC_DMA_REG_DCHERRMSK 48 + +/* HW dma desc */ +struct mic_dma_desc { + u64 qw0; + u64 qw1; +}; + +enum mic_dma_chan_owner { + MIC_DMA_CHAN_MIC = 0, + MIC_DMA_CHAN_HOST +}; + +/* + * mic_dma_chan - channel specific information + * @ch_num: channel number + * @owner: owner of this channel + * @last_tail: cached value of descriptor ring tail + * @head: index of next descriptor in desc_ring + * @issued: hardware notification point + * @submitted: index that will be used to submit descriptors to h/w + * @api_ch: dma engine api channel + * @desc_ring: dma descriptor ring + * @desc_ring_micpa: mic physical address of desc_ring + * @status_dest: destination for status (fence) descriptor + * @status_dest_micpa: mic address for status_dest, + * DMA controller uses this address + * @tx_array: array of async_tx + * @cleanup_lock: lock held when processing completed tx + * @prep_lock: lock held in prep_memcpy & released in tx_submit + * @issue_lock: lock used to synchronize writes to head + * @cookie: mic_irq cookie used with mic irq request + */ +struct mic_dma_chan { + int ch_num; + enum mic_dma_chan_owner owner; + u32 last_tail; + u32 head; + u32 issued; + u32 submitted; + struct dma_chan api_ch; + struct mic_dma_desc *desc_ring; + dma_addr_t desc_ring_micpa; + u64 *status_dest; + dma_addr_t status_dest_micpa; + struct dma_async_tx_descriptor *tx_array; + spinlock_t cleanup_lock; + spinlock_t prep_lock; + spinlock_t issue_lock; + struct mic_irq *cookie; +}; + +/* + * struct mic_dma_device - per mic device + * @mic_ch: dma channels + * @dma_dev: underlying dma device + * @mbdev: mic bus dma device + * @mmio: virtual address of the mmio space + * @dbg_dir: debugfs directory + * @start_ch: first channel number that can be used + * @max_xfer_size: maximum transfer size per dma descriptor + */ +struct mic_dma_device { + struct mic_dma_chan mic_ch[MIC_DMA_MAX_NUM_CHAN]; + struct dma_device dma_dev; + struct mbus_device *mbdev; + void __iomem *mmio; + struct dentry *dbg_dir; + int start_ch; + size_t max_xfer_size; +}; + +static inline struct mic_dma_chan *to_mic_dma_chan(struct dma_chan *ch) +{ + return container_of(ch, struct mic_dma_chan, api_ch); +} + +static inline struct mic_dma_device *to_mic_dma_dev(struct mic_dma_chan *ch) +{ + return + container_of((const typeof(((struct mic_dma_device *)0)->mic_ch)*) + (ch - ch->ch_num), struct mic_dma_device, mic_ch); +} + +static inline struct mbus_device *to_mbus_device(struct mic_dma_chan *ch) +{ + return to_mic_dma_dev(ch)->mbdev; +} + +static inline struct mbus_hw_ops *to_mbus_hw_ops(struct mic_dma_chan *ch) +{ + return to_mbus_device(ch)->hw_ops; +} + +static inline struct device *mic_dma_ch_to_device(struct mic_dma_chan *ch) +{ + return to_mic_dma_dev(ch)->dma_dev.dev; +} + +static inline void __iomem *mic_dma_chan_to_mmio(struct mic_dma_chan *ch) +{ + return to_mic_dma_dev(ch)->mmio; +} + +static inline u32 mic_dma_read_reg(struct mic_dma_chan *ch, u32 reg) +{ + return ioread32(mic_dma_chan_to_mmio(ch) + MIC_DMA_SBOX_CH_BASE + + ch->ch_num * MIC_DMA_SBOX_CHAN_OFF + reg); +} + +static inline void mic_dma_write_reg(struct mic_dma_chan *ch, u32 reg, u32 val) +{ + iowrite32(val, mic_dma_chan_to_mmio(ch) + MIC_DMA_SBOX_CH_BASE + + ch->ch_num * MIC_DMA_SBOX_CHAN_OFF + reg); +} + +static inline u32 mic_dma_mmio_read(struct mic_dma_chan *ch, u32 offset) +{ + return ioread32(mic_dma_chan_to_mmio(ch) + offset); +} + +static inline void mic_dma_mmio_write(struct mic_dma_chan *ch, u32 val, + u32 offset) +{ + iowrite32(val, mic_dma_chan_to_mmio(ch) + offset); +} + +static inline u32 mic_dma_read_cmp_cnt(struct mic_dma_chan *ch) +{ + return mic_dma_read_reg(ch, MIC_DMA_REG_DSTAT) & + MIC_DMA_HW_CMP_CNT_MASK; +} + +static inline void mic_dma_chan_set_owner(struct mic_dma_chan *ch) +{ + u32 dcr = mic_dma_mmio_read(ch, MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR); + u32 chan_num = ch->ch_num; + + dcr = (dcr & ~(0x1 << (chan_num * 2))) | (ch->owner << (chan_num * 2)); + mic_dma_mmio_write(ch, dcr, MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR); +} + +static inline void mic_dma_enable_chan(struct mic_dma_chan *ch) +{ + u32 dcr = mic_dma_mmio_read(ch, MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR); + + dcr |= 2 << (ch->ch_num << 1); + mic_dma_mmio_write(ch, dcr, MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR); +} + +static inline void mic_dma_disable_chan(struct mic_dma_chan *ch) +{ + u32 dcr = mic_dma_mmio_read(ch, MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR); + + dcr &= ~(2 << (ch->ch_num << 1)); + mic_dma_mmio_write(ch, dcr, MIC_DMA_SBOX_BASE + MIC_DMA_SBOX_DCR); +} + +static void mic_dma_chan_set_desc_ring(struct mic_dma_chan *ch) +{ + u32 drar_hi; + dma_addr_t desc_ring_micpa = ch->desc_ring_micpa; + + drar_hi = (MIC_DMA_DESC_RX_SIZE & 0x1ffff) << 4; + if (MIC_DMA_CHAN_MIC == ch->owner) { + drar_hi |= (desc_ring_micpa >> 32) & 0xf; + } else { + drar_hi |= MIC_DMA_SBOX_DRARHI_SYS_MASK; + drar_hi |= ((desc_ring_micpa >> 34) + & 0x1f) << 21; + drar_hi |= (desc_ring_micpa >> 32) & 0x3; + } + mic_dma_write_reg(ch, MIC_DMA_REG_DRAR_LO, (u32) desc_ring_micpa); + mic_dma_write_reg(ch, MIC_DMA_REG_DRAR_HI, drar_hi); +} + +static inline void mic_dma_chan_mask_intr(struct mic_dma_chan *ch) +{ + u32 dcar = mic_dma_read_reg(ch, MIC_DMA_REG_DCAR); + + if (MIC_DMA_CHAN_MIC == ch->owner) + dcar |= MIC_DMA_SBOX_DCAR_IM0; + else + dcar |= MIC_DMA_SBOX_DCAR_IM1; + mic_dma_write_reg(ch, MIC_DMA_REG_DCAR, dcar); +} + +static inline void mic_dma_chan_unmask_intr(struct mic_dma_chan *ch) +{ + u32 dcar = mic_dma_read_reg(ch, MIC_DMA_REG_DCAR); + + if (MIC_DMA_CHAN_MIC == ch->owner) + dcar &= ~MIC_DMA_SBOX_DCAR_IM0; + else + dcar &= ~MIC_DMA_SBOX_DCAR_IM1; + mic_dma_write_reg(ch, MIC_DMA_REG_DCAR, dcar); +} + +static void mic_dma_ack_interrupt(struct mic_dma_chan *ch) +{ + if (MIC_DMA_CHAN_MIC == ch->owner) { + /* HW errata */ + mic_dma_chan_mask_intr(ch); + mic_dma_chan_unmask_intr(ch); + } + to_mbus_hw_ops(ch)->ack_interrupt(to_mbus_device(ch), ch->ch_num); +} +#endif -- cgit v1.2.3 From 2a211f320ee3d86835b40efd2948642482d3c933 Mon Sep 17 00:00:00 2001 From: Himangi Saraogi Date: Sat, 12 Jul 2014 01:12:43 +0530 Subject: thunderbolt: Use kcalloc The advantage of kcalloc is, that will prevent integer overflows which could result from the multiplication of number of elements and size and it is also a bit nicer to read. Signed-off-by: Himangi Saraogi Acked-by: Julia Lawall Acked-by: Andreas Noever Signed-off-by: Greg Kroah-Hartman --- drivers/thunderbolt/nhi.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/thunderbolt/nhi.c b/drivers/thunderbolt/nhi.c index ce72f31fe0d8..c68fe1222c16 100644 --- a/drivers/thunderbolt/nhi.c +++ b/drivers/thunderbolt/nhi.c @@ -569,12 +569,10 @@ static int nhi_probe(struct pci_dev *pdev, const struct pci_device_id *id) nhi->hop_count); INIT_WORK(&nhi->interrupt_work, nhi_interrupt_work); - nhi->tx_rings = devm_kzalloc(&pdev->dev, - nhi->hop_count * sizeof(*nhi->tx_rings), - GFP_KERNEL); - nhi->rx_rings = devm_kzalloc(&pdev->dev, - nhi->hop_count * sizeof(*nhi->rx_rings), - GFP_KERNEL); + nhi->tx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + sizeof(*nhi->tx_rings), GFP_KERNEL); + nhi->rx_rings = devm_kcalloc(&pdev->dev, nhi->hop_count, + sizeof(*nhi->rx_rings), GFP_KERNEL); if (!nhi->tx_rings || !nhi->rx_rings) return -ENOMEM; -- cgit v1.2.3 From e1ada0f2c2286f113870b186a563eaee86d80f3c Mon Sep 17 00:00:00 2001 From: Himangi Saraogi Date: Mon, 14 Jul 2014 14:23:51 -0700 Subject: misc: mic: Introduce the managed version of ioremap This patch moves data allocated using ioremap to managed data allocated using devm_ioremap and cleans now unnecessary iounmaps in probe and remove functions. Also the unnecessary label iounmap is done away with. Link: https://lkml.org/lkml/2014/6/1/191 Tested-by: Sudeep Dutt Signed-off-by: Himangi Saraogi Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mic/card/mic_x100.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/misc/mic/card/mic_x100.c b/drivers/misc/mic/card/mic_x100.c index 55c9465bd260..9d57545d64f6 100644 --- a/drivers/misc/mic/card/mic_x100.c +++ b/drivers/misc/mic/card/mic_x100.c @@ -200,7 +200,8 @@ static int __init mic_probe(struct platform_device *pdev) mdev->mmio.pa = MIC_X100_MMIO_BASE; mdev->mmio.len = MIC_X100_MMIO_LEN; - mdev->mmio.va = ioremap(MIC_X100_MMIO_BASE, MIC_X100_MMIO_LEN); + mdev->mmio.va = devm_ioremap(&pdev->dev, MIC_X100_MMIO_BASE, + MIC_X100_MMIO_LEN); if (!mdev->mmio.va) { dev_err(&pdev->dev, "Cannot remap MMIO BAR\n"); rc = -EIO; @@ -214,7 +215,7 @@ static int __init mic_probe(struct platform_device *pdev) if (IS_ERR(mdrv->dma_mbdev)) { rc = PTR_ERR(mdrv->dma_mbdev); dev_err(&pdev->dev, "mbus_add_device failed rc %d\n", rc); - goto iounmap; + goto done; } rc = mic_driver_init(mdrv); if (rc) { @@ -225,19 +226,15 @@ done: return rc; remove_dma: mbus_unregister_device(mdrv->dma_mbdev); -iounmap: - iounmap(mdev->mmio.va); return rc; } static int mic_remove(struct platform_device *pdev) { struct mic_driver *mdrv = &g_drv; - struct mic_device *mdev = &mdrv->mdev; mic_driver_uninit(mdrv); mbus_unregister_device(mdrv->dma_mbdev); - iounmap(mdev->mmio.va); return 0; } -- cgit v1.2.3 From ce05b68692f0e366384de96ddd07821c7bf364be Mon Sep 17 00:00:00 2001 From: Sudeep Dutt Date: Mon, 14 Jul 2014 14:23:52 -0700 Subject: dma: Fix MIC X100 DMA Driver Kconfig option Select DMA_ENGINE instead of DMAENGINE and delete the default line as the default is 'n' anyways. Link: https://lkml.org/lkml/2014/7/14/90 Reported-by: Paul Bolle Signed-off-by: Sudeep Dutt Signed-off-by: Greg Kroah-Hartman --- drivers/dma/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 7c8b8c41f4f4..8f6afbf9ba54 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -36,8 +36,7 @@ comment "DMA Devices" config INTEL_MIC_X100_DMA tristate "Intel MIC X100 DMA Driver" depends on 64BIT && X86 && INTEL_MIC_BUS - select DMAENGINE - default N + select DMA_ENGINE help This enables DMA support for the Intel Many Integrated Core (MIC) family of PCIe form factor coprocessor X100 devices that -- cgit v1.2.3 From 3e37ebb7183f0c4eb92a88c60657ac319c01b3e9 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 17 Jul 2014 10:53:34 +0300 Subject: mei: reset client connection state on timeout On connection timeout we leave the connecting client in connecting state. Since a new connection is stalled till previous connection is completed in this case no new connection is possible till the user space does release the file handle. Therefore on timeout we move the client to disconnected state. Cc: stable@vger.kernel.org # 3.15+ Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 59d20c599b16..9f8ab28bcb60 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -616,6 +616,7 @@ int mei_cl_connect(struct mei_cl *cl, struct file *file) mutex_lock(&dev->device_lock); if (cl->state != MEI_FILE_CONNECTED) { + cl->state = MEI_FILE_DISCONNECTED; /* something went really wrong */ if (!cl->status) cl->status = -EFAULT; -- cgit v1.2.3 From 22b987a325701223f9a37db700c6eb20b9924c6f Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 17 Jul 2014 10:53:35 +0300 Subject: mei: start disconnect request timer consistently Link must be reset in case the fw doesn't respond to client disconnect request. We did charge the timer only in irq path from mei_cl_irq_close and not in mei_cl_disconnect Cc: stable@vger.kernel.org # 3.10+ Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 9f8ab28bcb60..65545007745e 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -491,6 +491,7 @@ int mei_cl_disconnect(struct mei_cl *cl) cl_err(dev, cl, "failed to disconnect.\n"); goto free; } + cl->timer_count = MEI_CONNECT_TIMEOUT; mdelay(10); /* Wait for hardware disconnection ready */ list_add_tail(&cb->list, &dev->ctrl_rd_list.list); } else { -- cgit v1.2.3 From d5d83f8abea13d0b50ee762276c6c900d1946264 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 17 Jul 2014 10:53:36 +0300 Subject: mei: don't schedule suspend in pm idle Calling pm_schedule_suspend from the runtime pm idle callback may reschedule existing timer, thus in case of frequent runtime rpm idle call the suspend maybe starved. Instead we call pm_runtime_autosuspend which is checking if the timer is already charged. An example is monitoring device pci config space. Pci config sysfs handlers calls pci_config_pm_runtime_put/get helpers which in turns calls to device idle callback Cc: stable@vger.kernel.org # 3.15+ Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/pci-me.c | 2 +- drivers/misc/mei/pci-txe.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c index f0da3c91f0a8..a0e9422b55a2 100644 --- a/drivers/misc/mei/pci-me.c +++ b/drivers/misc/mei/pci-me.c @@ -369,7 +369,7 @@ static int mei_me_pm_runtime_idle(struct device *device) if (!dev) return -ENODEV; if (mei_write_is_idle(dev)) - pm_schedule_suspend(device, MEI_ME_RPM_TIMEOUT * 2); + pm_runtime_autosuspend(device); return -EBUSY; } diff --git a/drivers/misc/mei/pci-txe.c b/drivers/misc/mei/pci-txe.c index d7480fe8994d..19de57368b7a 100644 --- a/drivers/misc/mei/pci-txe.c +++ b/drivers/misc/mei/pci-txe.c @@ -306,7 +306,7 @@ static int mei_txe_pm_runtime_idle(struct device *device) if (!dev) return -ENODEV; if (mei_write_is_idle(dev)) - pm_schedule_suspend(device, MEI_TXI_RPM_TIMEOUT * 2); + pm_runtime_autosuspend(device); return -EBUSY; } -- cgit v1.2.3 From fe2f17eb3da38ac0d5a00c511255bf3a33d16d24 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Thu, 17 Jul 2014 10:53:38 +0300 Subject: mei: fix return value on disconnect timeout wait_event_timeout can return 0 or the remaining jiffies so return -ETIME if disconnected state not reached. Cc: stable@vger.kernel.org # 3.10+ Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/client.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/misc/mei/client.c b/drivers/misc/mei/client.c index 65545007745e..324e1de93687 100644 --- a/drivers/misc/mei/client.c +++ b/drivers/misc/mei/client.c @@ -459,7 +459,7 @@ int mei_cl_disconnect(struct mei_cl *cl) { struct mei_device *dev; struct mei_cl_cb *cb; - int rets, err; + int rets; if (WARN_ON(!cl || !cl->dev)) return -ENODEV; @@ -501,23 +501,18 @@ int mei_cl_disconnect(struct mei_cl *cl) } mutex_unlock(&dev->device_lock); - err = wait_event_timeout(dev->wait_recvd_msg, + wait_event_timeout(dev->wait_recvd_msg, MEI_FILE_DISCONNECTED == cl->state, mei_secs_to_jiffies(MEI_CL_CONNECT_TIMEOUT)); mutex_lock(&dev->device_lock); + if (MEI_FILE_DISCONNECTED == cl->state) { rets = 0; cl_dbg(dev, cl, "successfully disconnected from FW client.\n"); } else { - rets = -ENODEV; - if (MEI_FILE_DISCONNECTED != cl->state) - cl_err(dev, cl, "wrong status client disconnect.\n"); - - if (err) - cl_dbg(dev, cl, "wait failed disconnect err=%d\n", err); - - cl_err(dev, cl, "failed to disconnect from FW client.\n"); + cl_dbg(dev, cl, "timeout on disconnect from FW client.\n"); + rets = -ETIME; } mei_io_list_flush(&dev->ctrl_rd_list, cl); -- cgit v1.2.3 From 389345cf6d71914b39130b1ea04dab1b7bacf6bd Mon Sep 17 00:00:00 2001 From: Andrey Utkin Date: Thu, 17 Jul 2014 15:18:53 +0300 Subject: drivers/char/dsp56k.c: drop check for negativity of unsigned parameter [linux-3.16-rc5/drivers/char/dsp56k.c:386]: (style) Checking if unsigned variable 'arg' is less than zero. Source code is if (arg > 31 || arg < 0) return -EINVAL; But static long dsp56k_ioctl(struct file *file, unsigned int cmd, unsigned long arg) Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=80411 Reported-by: David Binderman Signed-off-by: Andrey Utkin Signed-off-by: Greg Kroah-Hartman --- drivers/char/dsp56k.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/char/dsp56k.c b/drivers/char/dsp56k.c index 01a5ca7425d7..8bf70e8c3f79 100644 --- a/drivers/char/dsp56k.c +++ b/drivers/char/dsp56k.c @@ -383,7 +383,7 @@ static long dsp56k_ioctl(struct file *file, unsigned int cmd, return put_user(status, &hf->status); } case DSP56K_HOST_CMD: - if (arg > 31 || arg < 0) + if (arg > 31) return -EINVAL; mutex_lock(&dsp56k_mutex); dsp56k_host_interface.cvr = (u_char)((arg & DSP56K_CVR_HV_MASK) | -- cgit v1.2.3 From 7426d29ea1564c147d8401fb42d6f3d127db6bdd Mon Sep 17 00:00:00 2001 From: Benoit Taine Date: Fri, 18 Jul 2014 17:27:12 +0200 Subject: ipack: Replace DEFINE_PCI_DEVICE_TABLE macro use We should prefer `struct pci_device_id` over `DEFINE_PCI_DEVICE_TABLE` to meet kernel coding style guidelines. This issue was reported by checkpatch. Signed-off-by: Benoit Taine Acked-by: Samuel Iglesias Gonsalvez Signed-off-by: Greg Kroah-Hartman --- drivers/ipack/carriers/tpci200.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/ipack/carriers/tpci200.c b/drivers/ipack/carriers/tpci200.c index c276fde318e5..de5e32151a1e 100644 --- a/drivers/ipack/carriers/tpci200.c +++ b/drivers/ipack/carriers/tpci200.c @@ -618,7 +618,7 @@ static void tpci200_pci_remove(struct pci_dev *dev) __tpci200_pci_remove(tpci200); } -static DEFINE_PCI_DEVICE_TABLE(tpci200_idtable) = { +static const struct pci_device_id tpci200_idtable[] = { { TPCI200_VENDOR_ID, TPCI200_DEVICE_ID, TPCI200_SUBVENDOR_ID, TPCI200_SUBDEVICE_ID }, { 0, }, -- cgit v1.2.3 From 32182cd39d94fa9586cc08c74fdb30fb718f712d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 18 Jul 2014 16:54:23 -0700 Subject: misc: remove DEFINE_PCI_DEVICE_TABLE usage Removes DEFINE_PCI_DEVICE_TABLE from drivers/misc/genwqe/card_base.c, drivers/misc/genwqe/card_base.c, and drivers/misc/vmw_vmci/vmci_guest.c in preferance of a "real" structure definition. Signed-off-by: Greg Kroah-Hartman --- drivers/misc/genwqe/card_base.c | 2 +- drivers/misc/mic/host/mic_main.c | 2 +- drivers/misc/vmw_vmci/vmci_guest.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/misc/genwqe/card_base.c b/drivers/misc/genwqe/card_base.c index dc8c04ae5113..43bbabc96b6c 100644 --- a/drivers/misc/genwqe/card_base.c +++ b/drivers/misc/genwqe/card_base.c @@ -57,7 +57,7 @@ static struct dentry *debugfs_genwqe; static struct genwqe_dev *genwqe_devices[GENWQE_CARD_NO_MAX]; /* PCI structure for identifying device by PCI vendor and device ID */ -static DEFINE_PCI_DEVICE_TABLE(genwqe_device_table) = { +static const struct pci_device_id genwqe_device_table[] = { { .vendor = PCI_VENDOR_ID_IBM, .device = PCI_DEVICE_GENWQE, .subvendor = PCI_SUBVENDOR_ID_IBM, diff --git a/drivers/misc/mic/host/mic_main.c b/drivers/misc/mic/host/mic_main.c index fdc9c13430e7..ab37a3117d23 100644 --- a/drivers/misc/mic/host/mic_main.c +++ b/drivers/misc/mic/host/mic_main.c @@ -38,7 +38,7 @@ static const char mic_driver_name[] = "mic"; -static DEFINE_PCI_DEVICE_TABLE(mic_pci_tbl) = { +static const struct pci_device_id mic_pci_tbl[] = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2250)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2251)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, MIC_X100_PCI_DEVICE_2252)}, diff --git a/drivers/misc/vmw_vmci/vmci_guest.c b/drivers/misc/vmw_vmci/vmci_guest.c index e0d5017785e5..248399a881af 100644 --- a/drivers/misc/vmw_vmci/vmci_guest.c +++ b/drivers/misc/vmw_vmci/vmci_guest.c @@ -748,7 +748,7 @@ static void vmci_guest_remove_device(struct pci_dev *pdev) /* The rest are managed resources and will be freed by PCI core */ } -static DEFINE_PCI_DEVICE_TABLE(vmci_ids) = { +static const struct pci_device_id vmci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_VMWARE, PCI_DEVICE_ID_VMWARE_VMCI), }, { 0 }, }; -- cgit v1.2.3 From 0178a7a54d408d2c5b7bbe7eee9450bffbec0989 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 18 Jul 2014 16:58:07 -0700 Subject: pcmcia: remove DEFINE_PCI_DEVICE_TABLE usage It's not needed, just use the "real" structure definition instead. Signed-off-by: Greg Kroah-Hartman --- drivers/pcmcia/bcm63xx_pcmcia.c | 2 +- drivers/pcmcia/i82092.c | 2 +- drivers/pcmcia/pd6729.c | 2 +- drivers/pcmcia/vrc4173_cardu.c | 2 +- drivers/pcmcia/yenta_socket.c | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/pcmcia/bcm63xx_pcmcia.c b/drivers/pcmcia/bcm63xx_pcmcia.c index 0c6aac1232fc..0802e0bc7d0c 100644 --- a/drivers/pcmcia/bcm63xx_pcmcia.c +++ b/drivers/pcmcia/bcm63xx_pcmcia.c @@ -475,7 +475,7 @@ static void bcm63xx_cb_exit(struct pci_dev *dev) bcm63xx_cb_dev = NULL; } -static DEFINE_PCI_DEVICE_TABLE(bcm63xx_cb_table) = { +static const struct pci_device_id bcm63xx_cb_table[] = { { .vendor = PCI_VENDOR_ID_BROADCOM, .device = BCM6348_CPU_ID, diff --git a/drivers/pcmcia/i82092.c b/drivers/pcmcia/i82092.c index 7d47456429a1..aae7e6df99cd 100644 --- a/drivers/pcmcia/i82092.c +++ b/drivers/pcmcia/i82092.c @@ -25,7 +25,7 @@ MODULE_LICENSE("GPL"); /* PCI core routines */ -static DEFINE_PCI_DEVICE_TABLE(i82092aa_pci_ids) = { +static const struct pci_device_id i82092aa_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82092AA_0) }, { } }; diff --git a/drivers/pcmcia/pd6729.c b/drivers/pcmcia/pd6729.c index 622dd6fe7347..34ace4854dc2 100644 --- a/drivers/pcmcia/pd6729.c +++ b/drivers/pcmcia/pd6729.c @@ -764,7 +764,7 @@ static void pd6729_pci_remove(struct pci_dev *dev) kfree(socket); } -static DEFINE_PCI_DEVICE_TABLE(pd6729_pci_ids) = { +static const struct pci_device_id pd6729_pci_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_CIRRUS, PCI_DEVICE_ID_CIRRUS_6729) }, { } }; diff --git a/drivers/pcmcia/vrc4173_cardu.c b/drivers/pcmcia/vrc4173_cardu.c index d92692056e24..9fb0c3addfd4 100644 --- a/drivers/pcmcia/vrc4173_cardu.c +++ b/drivers/pcmcia/vrc4173_cardu.c @@ -563,7 +563,7 @@ static int vrc4173_cardu_setup(char *options) __setup("vrc4173_cardu=", vrc4173_cardu_setup); -static DEFINE_PCI_DEVICE_TABLE(vrc4173_cardu_id_table) = { +static const struct pci_device_id vrc4173_cardu_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_NAPCCARD) }, {0, } }; diff --git a/drivers/pcmcia/yenta_socket.c b/drivers/pcmcia/yenta_socket.c index 946f90ef6020..8a23ccb41213 100644 --- a/drivers/pcmcia/yenta_socket.c +++ b/drivers/pcmcia/yenta_socket.c @@ -1352,7 +1352,7 @@ static const struct dev_pm_ops yenta_pm_ops = { .driver_data = CARDBUS_TYPE_##type, \ } -static DEFINE_PCI_DEVICE_TABLE(yenta_table) = { +static const struct pci_device_id yenta_table[] = { CB_ID(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_1031, TI), /* -- cgit v1.2.3 From 4734e39064219fca321ddbc930b46e7c3c194143 Mon Sep 17 00:00:00 2001 From: Himangi Saraogi Date: Mon, 21 Jul 2014 13:55:35 +0530 Subject: misc: bh1770glc: Use managed functions This patch introduces the use of managed interfaces like devm_kzalloc, devm_regulator_bulk_get and does away with the functions to free the allocated memory in the probe and remove functions. Also, some labels are removed and renamed to preserve the ordering. Signed-off-by: Himangi Saraogi Acked-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/misc/bh1770glc.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/drivers/misc/bh1770glc.c b/drivers/misc/bh1770glc.c index 99a04686e45f..7b55f8a152d4 100644 --- a/drivers/misc/bh1770glc.c +++ b/drivers/misc/bh1770glc.c @@ -1185,7 +1185,7 @@ static int bh1770_probe(struct i2c_client *client, struct bh1770_chip *chip; int err; - chip = kzalloc(sizeof *chip, GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof *chip, GFP_KERNEL); if (!chip) return -ENOMEM; @@ -1198,8 +1198,7 @@ static int bh1770_probe(struct i2c_client *client, if (client->dev.platform_data == NULL) { dev_err(&client->dev, "platform data is mandatory\n"); - err = -EINVAL; - goto fail1; + return -EINVAL; } chip->pdata = client->dev.platform_data; @@ -1224,24 +1223,24 @@ static int bh1770_probe(struct i2c_client *client, chip->regs[0].supply = reg_vcc; chip->regs[1].supply = reg_vleds; - err = regulator_bulk_get(&client->dev, - ARRAY_SIZE(chip->regs), chip->regs); + err = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(chip->regs), chip->regs); if (err < 0) { dev_err(&client->dev, "Cannot get regulators\n"); - goto fail1; + return err; } err = regulator_bulk_enable(ARRAY_SIZE(chip->regs), chip->regs); if (err < 0) { dev_err(&client->dev, "Cannot enable regulators\n"); - goto fail2; + return err; } usleep_range(BH1770_STARTUP_DELAY, BH1770_STARTUP_DELAY * 2); err = bh1770_detect(chip); if (err < 0) - goto fail3; + goto fail0; /* Start chip */ bh1770_chip_on(chip); @@ -1252,14 +1251,14 @@ static int bh1770_probe(struct i2c_client *client, if (chip->lux_corr == 0) { dev_err(&client->dev, "Improper correction values\n"); err = -EINVAL; - goto fail3; + goto fail0; } if (chip->pdata->setup_resources) { err = chip->pdata->setup_resources(); if (err) { err = -EINVAL; - goto fail3; + goto fail0; } } @@ -1267,7 +1266,7 @@ static int bh1770_probe(struct i2c_client *client, &bh1770_attribute_group); if (err < 0) { dev_err(&chip->client->dev, "Sysfs registration failed\n"); - goto fail4; + goto fail1; } /* @@ -1283,22 +1282,18 @@ static int bh1770_probe(struct i2c_client *client, if (err) { dev_err(&client->dev, "could not get IRQ %d\n", client->irq); - goto fail5; + goto fail2; } regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); return err; -fail5: +fail2: sysfs_remove_group(&chip->client->dev.kobj, &bh1770_attribute_group); -fail4: +fail1: if (chip->pdata->release_resources) chip->pdata->release_resources(); -fail3: +fail0: regulator_bulk_disable(ARRAY_SIZE(chip->regs), chip->regs); -fail2: - regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); -fail1: - kfree(chip); return err; } @@ -1322,8 +1317,6 @@ static int bh1770_remove(struct i2c_client *client) pm_runtime_disable(&client->dev); pm_runtime_set_suspended(&client->dev); - regulator_bulk_free(ARRAY_SIZE(chip->regs), chip->regs); - kfree(chip); return 0; } -- cgit v1.2.3 From 8f642155c5ddafe1247c085f3f0c7b4e63044878 Mon Sep 17 00:00:00 2001 From: Alexander Usyskin Date: Sun, 20 Jul 2014 13:36:31 +0300 Subject: mei: drop unused hw dependent fw status functions We introduced unified FW status function in patch mei: add per device configuration (lkml.org/lkml/2014/5/12/607) This change made hw_ops functions unused and obsolete therefore we remove these functions from source code. Signed-off-by: Alexander Usyskin Signed-off-by: Tomas Winkler Signed-off-by: Greg Kroah-Hartman --- drivers/misc/mei/hw-me.c | 54 ---------------------------------------------- drivers/misc/mei/hw-txe.c | 32 --------------------------- drivers/misc/mei/mei_dev.h | 3 --- 3 files changed, 89 deletions(-) diff --git a/drivers/misc/mei/hw-me.c b/drivers/misc/mei/hw-me.c index 6a2d272cea43..a9a0d08f758e 100644 --- a/drivers/misc/mei/hw-me.c +++ b/drivers/misc/mei/hw-me.c @@ -710,64 +710,10 @@ end: 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.c b/drivers/misc/mei/hw-txe.c index 93273783dec5..f1cd166094f2 100644 --- a/drivers/misc/mei/hw-txe.c +++ b/drivers/misc/mei/hw-txe.c @@ -1042,40 +1042,8 @@ 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/mei_dev.h b/drivers/misc/mei/mei_dev.h index 2afa3d69107d..0b0d6135543b 100644 --- a/drivers/misc/mei/mei_dev.h +++ b/drivers/misc/mei/mei_dev.h @@ -227,7 +227,6 @@ 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 @@ -255,8 +254,6 @@ 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); -- cgit v1.2.3 From f222447ea1b70a651ec280bfb4a2fde4cbeae983 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sun, 20 Jul 2014 13:25:39 +0800 Subject: misc: vexpress: Fix sparse non static symbol warnings Fixes the following sparse warnings: drivers/misc/vexpress-syscfg.c:133:22: warning: symbol 'vexpress_syscfg_regmap_config' was not declared. Should it be static? drivers/misc/vexpress-syscfg.c:279:5: warning: symbol 'vexpress_syscfg_probe' was not declared. Should it be static? Signed-off-by: Wei Yongjun Signed-off-by: Greg Kroah-Hartman --- drivers/misc/vexpress-syscfg.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/misc/vexpress-syscfg.c b/drivers/misc/vexpress-syscfg.c index 3250fc1df0aa..b3a812384a6f 100644 --- a/drivers/misc/vexpress-syscfg.c +++ b/drivers/misc/vexpress-syscfg.c @@ -130,7 +130,7 @@ static int vexpress_syscfg_write(void *context, unsigned int index, return vexpress_syscfg_exec(func, index, true, &val); } -struct regmap_config vexpress_syscfg_regmap_config = { +static struct regmap_config vexpress_syscfg_regmap_config = { .lock = vexpress_config_lock, .unlock = vexpress_config_unlock, .reg_bits = 32, @@ -276,7 +276,7 @@ int vexpress_syscfg_device_register(struct platform_device *pdev) } -int vexpress_syscfg_probe(struct platform_device *pdev) +static int vexpress_syscfg_probe(struct platform_device *pdev) { struct vexpress_syscfg *syscfg; struct resource *res; -- cgit v1.2.3 From 0a16ee633a83374a2bedbd6a057ab7d7e50a1d50 Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Wed, 23 Jul 2014 10:07:09 +0900 Subject: extcon: Remove unnecessary OOM messages The site-specific OOM messages are unnecessary, because they duplicate the MM subsystem generic OOM message. The following checkpatch warning is also removed. WARNING: Possible unnecessary 'out of memory' message Signed-off-by: Jingoo Han [Acked by Charles Keepax for arizona part] Acked-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 4 +--- drivers/extcon/extcon-max14577.c | 5 ++--- drivers/extcon/extcon-max77693.c | 5 ++--- drivers/extcon/extcon-max8997.c | 4 +--- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 0b1ee9f55378..59aa51309467 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1117,10 +1117,8 @@ static int arizona_extcon_probe(struct platform_device *pdev) return -EPROBE_DEFER; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "Failed to allocate memory\n"); + if (!info) return -ENOMEM; - } info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); if (IS_ERR(info->micvdd)) { diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index d49e891b5675..7309743d0da1 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -692,10 +692,9 @@ static int max14577_muic_probe(struct platform_device *pdev) u8 id; info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!info) return -ENOMEM; - } + info->dev = &pdev->dev; info->max14577 = max14577; diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 042bf742c207..77460f2c1ca1 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -1099,10 +1099,9 @@ static int max77693_muic_probe(struct platform_device *pdev) info = devm_kzalloc(&pdev->dev, sizeof(struct max77693_muic_info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!info) return -ENOMEM; - } + info->dev = &pdev->dev; info->max77693 = max77693; if (info->max77693->regmap_muic) { diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index d22c379766dd..75e501c98005 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -661,10 +661,8 @@ static int max8997_muic_probe(struct platform_device *pdev) info = devm_kzalloc(&pdev->dev, sizeof(struct max8997_muic_info), GFP_KERNEL); - if (!info) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!info) return -ENOMEM; - } info->dev = &pdev->dev; info->muic = max8997->muic; -- cgit v1.2.3 From 17271f608bf818a13b2c235d45258311308d5b03 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 18 Jul 2014 12:59:00 +0100 Subject: extcon: arizona: Get MICVDD against extcon device Previously we would do a regulator get against the main Arizona device to obtain the MICVDD regulator. Arizona is an MFD device and normally MICVDD will be supplied by one of its children (the arizona-micsupp regulator). As devres destruction for the MFD device will run after all its children have been destroyed, the regulator will be destroyed before devres calls regulator_put. This causes a warning from both the destruction of the child node, as the regulator is still open, and from the put of the regulator as the regulator device has already been destroyed. A simple fix here is to get the regulator against the extcon device itself such that devres runs when the child is destroyed. This has the additional benefit that if for some reason the extcon driver is unloaded the regulator reference won't hang around until the MFD is unloaded. Signed-off-by: Charles Keepax Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-arizona.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-arizona.c b/drivers/extcon/extcon-arizona.c index 59aa51309467..ba51588cc000 100644 --- a/drivers/extcon/extcon-arizona.c +++ b/drivers/extcon/extcon-arizona.c @@ -1120,7 +1120,7 @@ static int arizona_extcon_probe(struct platform_device *pdev) if (!info) return -ENOMEM; - info->micvdd = devm_regulator_get(arizona->dev, "MICVDD"); + info->micvdd = devm_regulator_get(&pdev->dev, "MICVDD"); if (IS_ERR(info->micvdd)) { ret = PTR_ERR(info->micvdd); dev_err(arizona->dev, "Failed to get MICVDD: %d\n", ret); -- cgit v1.2.3 From 914b881f9452fd615cc597b434fd8c0e12a7dae2 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Thu, 22 May 2014 14:06:33 +0900 Subject: extcon: sm5502: Add support new SM5502 extcon device driver This patch add new SM5502 MUIC(Micro-USB Interface Controller) device by using EXTCON subsystem. The extcon-sm5502 driver is capable of identifying the type of the external power source and attached accessory. An external power sources, such as Deticated Charger or a standard USB port, are able to charge the battery in the smart phone via the connector. Signed-off-by: Chanwoo Choi --- drivers/extcon/Kconfig | 10 + drivers/extcon/Makefile | 1 + drivers/extcon/extcon-sm5502.c | 620 +++++++++++++++++++++++++++++++++++++++++ include/linux/extcon/sm5502.h | 264 ++++++++++++++++++ 4 files changed, 895 insertions(+) create mode 100644 drivers/extcon/extcon-sm5502.c create mode 100644 include/linux/extcon/sm5502.h diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index 9125eba6b3d6..6f2f4727de2c 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -70,4 +70,14 @@ config EXTCON_PALMAS Say Y here to enable support for USB peripheral and USB host detection by palmas usb. +config EXTCON_SM5502 + tristate "SM5502 EXTCON support" + select IRQ_DOMAIN + select REGMAP_I2C + select REGMAP_IRQ + help + If you say yes here you get support for the MUIC device of + Silicon Mitus SM5502. The SM5502 is a USB port accessory + detector and switch. + endif # MULTISTATE_SWITCH diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index e48abc6d230f..b38546eb522a 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_EXTCON_MAX14577) += extcon-max14577.o obj-$(CONFIG_EXTCON_MAX77693) += extcon-max77693.o obj-$(CONFIG_EXTCON_MAX8997) += extcon-max8997.o obj-$(CONFIG_EXTCON_PALMAS) += extcon-palmas.o +obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o diff --git a/drivers/extcon/extcon-sm5502.c b/drivers/extcon/extcon-sm5502.c new file mode 100644 index 000000000000..9f318c222b89 --- /dev/null +++ b/drivers/extcon/extcon-sm5502.c @@ -0,0 +1,620 @@ +/* + * extcon-sm5502.c - Silicon Mitus SM5502 extcon drvier to support USB switches + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * Author: Chanwoo Choi + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct muic_irq { + unsigned int irq; + const char *name; + unsigned int virq; +}; + +struct reg_data { + u8 reg; + unsigned int val; + bool invert; +}; + +struct sm5502_muic_info { + struct device *dev; + struct extcon_dev *edev; + + struct i2c_client *i2c; + struct regmap *regmap; + + struct regmap_irq_chip_data *irq_data; + struct muic_irq *muic_irqs; + unsigned int num_muic_irqs; + int irq; + bool irq_attach; + bool irq_detach; + struct work_struct irq_work; + + struct reg_data *reg_data; + unsigned int num_reg_data; + + struct mutex mutex; +}; + +/* Default value of SM5502 register to bring up MUIC device. */ +static struct reg_data sm5502_reg_data[] = { + { + .reg = SM5502_REG_CONTROL, + .val = SM5502_REG_CONTROL_MASK_INT_MASK, + .invert = false, + }, { + .reg = SM5502_REG_INTMASK1, + .val = SM5502_REG_INTM1_KP_MASK + | SM5502_REG_INTM1_LKP_MASK + | SM5502_REG_INTM1_LKR_MASK, + .invert = true, + }, { + .reg = SM5502_REG_INTMASK2, + .val = SM5502_REG_INTM2_VBUS_DET_MASK + | SM5502_REG_INTM2_REV_ACCE_MASK + | SM5502_REG_INTM2_ADC_CHG_MASK + | SM5502_REG_INTM2_STUCK_KEY_MASK + | SM5502_REG_INTM2_STUCK_KEY_RCV_MASK + | SM5502_REG_INTM2_MHL_MASK, + .invert = true, + }, + { } +}; + +/* List of detectable cables */ +enum { + EXTCON_CABLE_USB = 0, + EXTCON_CABLE_USB_HOST, + EXTCON_CABLE_TA, + + EXTCON_CABLE_END, +}; + +static const char *sm5502_extcon_cable[] = { + [EXTCON_CABLE_USB] = "USB", + [EXTCON_CABLE_USB_HOST] = "USB-Host", + [EXTCON_CABLE_TA] = "TA", + NULL, +}; + +/* Define supported accessory type */ +enum sm5502_muic_acc_type { + SM5502_MUIC_ADC_GROUND = 0x0, + SM5502_MUIC_ADC_SEND_END_BUTTON, + SM5502_MUIC_ADC_REMOTE_S1_BUTTON, + SM5502_MUIC_ADC_REMOTE_S2_BUTTON, + SM5502_MUIC_ADC_REMOTE_S3_BUTTON, + SM5502_MUIC_ADC_REMOTE_S4_BUTTON, + SM5502_MUIC_ADC_REMOTE_S5_BUTTON, + SM5502_MUIC_ADC_REMOTE_S6_BUTTON, + SM5502_MUIC_ADC_REMOTE_S7_BUTTON, + SM5502_MUIC_ADC_REMOTE_S8_BUTTON, + SM5502_MUIC_ADC_REMOTE_S9_BUTTON, + SM5502_MUIC_ADC_REMOTE_S10_BUTTON, + SM5502_MUIC_ADC_REMOTE_S11_BUTTON, + SM5502_MUIC_ADC_REMOTE_S12_BUTTON, + SM5502_MUIC_ADC_RESERVED_ACC_1, + SM5502_MUIC_ADC_RESERVED_ACC_2, + SM5502_MUIC_ADC_RESERVED_ACC_3, + SM5502_MUIC_ADC_RESERVED_ACC_4, + SM5502_MUIC_ADC_RESERVED_ACC_5, + SM5502_MUIC_ADC_AUDIO_TYPE2, + SM5502_MUIC_ADC_PHONE_POWERED_DEV, + SM5502_MUIC_ADC_TTY_CONVERTER, + SM5502_MUIC_ADC_UART_CABLE, + SM5502_MUIC_ADC_TYPE1_CHARGER, + SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB, + SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB, + SM5502_MUIC_ADC_AUDIO_VIDEO_CABLE, + SM5502_MUIC_ADC_TYPE2_CHARGER, + SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART, + SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART, + SM5502_MUIC_ADC_AUDIO_TYPE1, + SM5502_MUIC_ADC_OPEN = 0x1f, + + /* The below accessories have same ADC value (0x1f or 0x1e). + So, Device type1 is used to separate specific accessory. */ + /* |---------|--ADC| */ + /* | [7:5]|[4:0]| */ + SM5502_MUIC_ADC_AUDIO_TYPE1_FULL_REMOTE = 0x3e, /* | 001|11110| */ + SM5502_MUIC_ADC_AUDIO_TYPE1_SEND_END = 0x5e, /* | 010|11110| */ + /* |Dev Type1|--ADC| */ + SM5502_MUIC_ADC_OPEN_USB = 0x5f, /* | 010|11111| */ + SM5502_MUIC_ADC_OPEN_TA = 0xdf, /* | 110|11111| */ + SM5502_MUIC_ADC_OPEN_USB_OTG = 0xff, /* | 111|11111| */ +}; + +/* List of supported interrupt for SM5502 */ +static struct muic_irq sm5502_muic_irqs[] = { + { SM5502_IRQ_INT1_ATTACH, "muic-attach" }, + { SM5502_IRQ_INT1_DETACH, "muic-detach" }, + { SM5502_IRQ_INT1_KP, "muic-kp" }, + { SM5502_IRQ_INT1_LKP, "muic-lkp" }, + { SM5502_IRQ_INT1_LKR, "muic-lkr" }, + { SM5502_IRQ_INT1_OVP_EVENT, "muic-ovp-event" }, + { SM5502_IRQ_INT1_OCP_EVENT, "muic-ocp-event" }, + { SM5502_IRQ_INT1_OVP_OCP_DIS, "muic-ovp-ocp-dis" }, + { SM5502_IRQ_INT2_VBUS_DET, "muic-vbus-det" }, + { SM5502_IRQ_INT2_REV_ACCE, "muic-rev-acce" }, + { SM5502_IRQ_INT2_ADC_CHG, "muic-adc-chg" }, + { SM5502_IRQ_INT2_STUCK_KEY, "muic-stuck-key" }, + { SM5502_IRQ_INT2_STUCK_KEY_RCV, "muic-stuck-key-rcv" }, + { SM5502_IRQ_INT2_MHL, "muic-mhl" }, +}; + +/* Define interrupt list of SM5502 to register regmap_irq */ +static const struct regmap_irq sm5502_irqs[] = { + /* INT1 interrupts */ + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_ATTACH_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_DETACH_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_KP_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_LKP_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_LKR_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_OVP_EVENT_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_OCP_EVENT_MASK, }, + { .reg_offset = 0, .mask = SM5502_IRQ_INT1_OVP_OCP_DIS_MASK, }, + + /* INT2 interrupts */ + { .reg_offset = 1, .mask = SM5502_IRQ_INT2_VBUS_DET_MASK,}, + { .reg_offset = 1, .mask = SM5502_IRQ_INT2_REV_ACCE_MASK, }, + { .reg_offset = 1, .mask = SM5502_IRQ_INT2_ADC_CHG_MASK, }, + { .reg_offset = 1, .mask = SM5502_IRQ_INT2_STUCK_KEY_MASK, }, + { .reg_offset = 1, .mask = SM5502_IRQ_INT2_STUCK_KEY_RCV_MASK, }, + { .reg_offset = 1, .mask = SM5502_IRQ_INT2_MHL_MASK, }, +}; + +static const struct regmap_irq_chip sm5502_muic_irq_chip = { + .name = "sm5502", + .status_base = SM5502_REG_INT1, + .mask_base = SM5502_REG_INTMASK1, + .mask_invert = false, + .num_regs = 2, + .irqs = sm5502_irqs, + .num_irqs = ARRAY_SIZE(sm5502_irqs), +}; + +/* Define regmap configuration of SM5502 for I2C communication */ +static bool sm5502_muic_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SM5502_REG_INTMASK1: + case SM5502_REG_INTMASK2: + return true; + default: + break; + } + return false; +} + +static const struct regmap_config sm5502_muic_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .volatile_reg = sm5502_muic_volatile_reg, + .max_register = SM5502_REG_END, +}; + +/* Return cable type of attached or detached accessories */ +static unsigned int sm5502_muic_get_cable_type(struct sm5502_muic_info *info) +{ + unsigned int cable_type = -1, adc, dev_type1; + int ret; + + /* Read ADC value according to external cable or button */ + ret = regmap_read(info->regmap, SM5502_REG_ADC, &adc); + if (ret) { + dev_err(info->dev, "failed to read ADC register\n"); + return ret; + } + + /* + * If ADC is SM5502_MUIC_ADC_GROUND(0x0), external cable hasn't + * connected with to MUIC device. + */ + cable_type &= SM5502_REG_ADC_MASK; + if (cable_type == SM5502_MUIC_ADC_GROUND) + return SM5502_MUIC_ADC_GROUND; + + switch (cable_type) { + case SM5502_MUIC_ADC_GROUND: + case SM5502_MUIC_ADC_SEND_END_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S1_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S2_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S3_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S4_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S5_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S6_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S7_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S8_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S9_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S10_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S11_BUTTON: + case SM5502_MUIC_ADC_REMOTE_S12_BUTTON: + case SM5502_MUIC_ADC_RESERVED_ACC_1: + case SM5502_MUIC_ADC_RESERVED_ACC_2: + case SM5502_MUIC_ADC_RESERVED_ACC_3: + case SM5502_MUIC_ADC_RESERVED_ACC_4: + case SM5502_MUIC_ADC_RESERVED_ACC_5: + case SM5502_MUIC_ADC_AUDIO_TYPE2: + case SM5502_MUIC_ADC_PHONE_POWERED_DEV: + case SM5502_MUIC_ADC_TTY_CONVERTER: + case SM5502_MUIC_ADC_UART_CABLE: + case SM5502_MUIC_ADC_TYPE1_CHARGER: + case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_USB: + case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_USB: + case SM5502_MUIC_ADC_AUDIO_VIDEO_CABLE: + case SM5502_MUIC_ADC_TYPE2_CHARGER: + case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_OFF_UART: + case SM5502_MUIC_ADC_FACTORY_MODE_BOOT_ON_UART: + break; + case SM5502_MUIC_ADC_AUDIO_TYPE1: + /* + * Check whether cable type is + * SM5502_MUIC_ADC_AUDIO_TYPE1_FULL_REMOTE + * or SM5502_MUIC_ADC_AUDIO_TYPE1_SEND_END + * by using Button event. + */ + break; + case SM5502_MUIC_ADC_OPEN: + ret = regmap_read(info->regmap, SM5502_REG_DEV_TYPE1, + &dev_type1); + if (ret) { + dev_err(info->dev, "failed to read DEV_TYPE1 reg\n"); + return ret; + } + + switch (dev_type1) { + case SM5502_REG_DEV_TYPE1_USB_SDP_MASK: + cable_type = SM5502_MUIC_ADC_OPEN_USB; + break; + case SM5502_REG_DEV_TYPE1_DEDICATED_CHG_MASK: + cable_type = SM5502_MUIC_ADC_OPEN_TA; + break; + case SM5502_REG_DEV_TYPE1_USB_OTG_MASK: + cable_type = SM5502_MUIC_ADC_OPEN_USB_OTG; + break; + default: + dev_dbg(info->dev, + "cannot identify the cable type: adc(0x%x) " + "dev_type1(0x%x)\n", adc, dev_type1); + return -EINVAL; + }; + break; + default: + dev_err(info->dev, + "failed to identify the cable type: adc(0x%x)\n", adc); + return -EINVAL; + }; + + return cable_type; +} + +static int sm5502_muic_cable_handler(struct sm5502_muic_info *info, + bool attached) +{ + static unsigned int prev_cable_type = SM5502_MUIC_ADC_GROUND; + const char **cable_names = info->edev->supported_cable; + unsigned int cable_type = SM5502_MUIC_ADC_GROUND; + unsigned int idx = 0; + + if (!cable_names) + return 0; + + /* Get the type of attached or detached cable */ + if (attached) + cable_type = sm5502_muic_get_cable_type(info); + else if (!attached) + cable_type = prev_cable_type; + prev_cable_type = cable_type; + + switch (cable_type) { + case SM5502_MUIC_ADC_OPEN_USB: + idx = EXTCON_CABLE_USB; + break; + case SM5502_MUIC_ADC_OPEN_TA: + idx = EXTCON_CABLE_TA; + break; + case SM5502_MUIC_ADC_OPEN_USB_OTG: + idx = EXTCON_CABLE_USB_HOST; + break; + default: + dev_dbg(info->dev, + "cannot handle this cable_type (0x%x)\n", cable_type); + return 0; + }; + + extcon_set_cable_state(info->edev, cable_names[idx], attached); + + return 0; +} + +static void sm5502_muic_irq_work(struct work_struct *work) +{ + struct sm5502_muic_info *info = container_of(work, + struct sm5502_muic_info, irq_work); + int ret = 0; + + if (!info->edev) + return; + + mutex_lock(&info->mutex); + + /* Detect attached or detached cables */ + if (info->irq_attach) { + ret = sm5502_muic_cable_handler(info, true); + info->irq_attach = false; + } + if (info->irq_detach) { + ret = sm5502_muic_cable_handler(info, false); + info->irq_detach = false; + } + + if (ret < 0) + dev_err(info->dev, "failed to handle MUIC interrupt\n"); + + mutex_unlock(&info->mutex); + + return; +} + +/* + * Sets irq_attach or irq_detach in sm5502_muic_info and returns 0. + * Returns -ESRCH if irq_type does not match registered IRQ for this dev type. + */ +static int sm5502_parse_irq(struct sm5502_muic_info *info, int irq_type) +{ + switch (irq_type) { + case SM5502_IRQ_INT1_ATTACH: + info->irq_attach = true; + break; + case SM5502_IRQ_INT1_DETACH: + info->irq_detach = true; + break; + case SM5502_IRQ_INT1_KP: + case SM5502_IRQ_INT1_LKP: + case SM5502_IRQ_INT1_LKR: + case SM5502_IRQ_INT1_OVP_EVENT: + case SM5502_IRQ_INT1_OCP_EVENT: + case SM5502_IRQ_INT1_OVP_OCP_DIS: + case SM5502_IRQ_INT2_VBUS_DET: + case SM5502_IRQ_INT2_REV_ACCE: + case SM5502_IRQ_INT2_ADC_CHG: + case SM5502_IRQ_INT2_STUCK_KEY: + case SM5502_IRQ_INT2_STUCK_KEY_RCV: + case SM5502_IRQ_INT2_MHL: + default: + break; + } + + return 0; +} + +static irqreturn_t sm5502_muic_irq_handler(int irq, void *data) +{ + struct sm5502_muic_info *info = data; + int i, irq_type = -1, ret; + + for (i = 0; i < info->num_muic_irqs; i++) + if (irq == info->muic_irqs[i].virq) + irq_type = info->muic_irqs[i].irq; + + ret = sm5502_parse_irq(info, irq_type); + if (ret < 0) { + dev_warn(info->dev, "cannot handle is interrupt:%d\n", + irq_type); + return IRQ_HANDLED; + } + schedule_work(&info->irq_work); + + return IRQ_HANDLED; +} + +static void sm5502_init_dev_type(struct sm5502_muic_info *info) +{ + unsigned int reg_data, vendor_id, version_id; + int i, ret; + + /* To test I2C, Print version_id and vendor_id of SM5502 */ + ret = regmap_read(info->regmap, SM5502_REG_DEVICE_ID, ®_data); + if (ret) { + dev_err(info->dev, + "failed to read DEVICE_ID register: %d\n", ret); + return; + } + + vendor_id = ((reg_data & SM5502_REG_DEVICE_ID_VENDOR_MASK) >> + SM5502_REG_DEVICE_ID_VENDOR_SHIFT); + version_id = ((reg_data & SM5502_REG_DEVICE_ID_VERSION_MASK) >> + SM5502_REG_DEVICE_ID_VERSION_SHIFT); + + dev_info(info->dev, "Device type: version: 0x%x, vendor: 0x%x\n", + version_id, vendor_id); + + /* Initiazle the register of SM5502 device to bring-up */ + for (i = 0; i < info->num_reg_data; i++) { + unsigned int val = 0; + + if (!info->reg_data[i].invert) + val |= ~info->reg_data[i].val; + else + val = info->reg_data[i].val; + regmap_write(info->regmap, info->reg_data[i].reg, val); + } +} + +static int sm5022_muic_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct device_node *np = i2c->dev.of_node; + struct sm5502_muic_info *info; + int i, ret, irq_flags; + + if (!np) + return -EINVAL; + + info = devm_kzalloc(&i2c->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + i2c_set_clientdata(i2c, info); + + info->dev = &i2c->dev; + info->i2c = i2c; + info->irq = i2c->irq; + info->muic_irqs = sm5502_muic_irqs; + info->num_muic_irqs = ARRAY_SIZE(sm5502_muic_irqs); + info->reg_data = sm5502_reg_data; + info->num_reg_data = ARRAY_SIZE(sm5502_reg_data); + + mutex_init(&info->mutex); + + INIT_WORK(&info->irq_work, sm5502_muic_irq_work); + + info->regmap = devm_regmap_init_i2c(i2c, &sm5502_muic_regmap_config); + if (IS_ERR(info->regmap)) { + ret = PTR_ERR(info->regmap); + dev_err(info->dev, "failed to allocate register map: %d\n", + ret); + return ret; + } + + /* Support irq domain for SM5502 MUIC device */ + irq_flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT | IRQF_SHARED; + ret = regmap_add_irq_chip(info->regmap, info->irq, irq_flags, 0, + &sm5502_muic_irq_chip, &info->irq_data); + if (ret != 0) { + dev_err(info->dev, "failed to request IRQ %d: %d\n", + info->irq, ret); + return ret; + } + + for (i = 0; i < info->num_muic_irqs; i++) { + struct muic_irq *muic_irq = &info->muic_irqs[i]; + unsigned int virq = 0; + + virq = regmap_irq_get_virq(info->irq_data, muic_irq->irq); + if (virq <= 0) + return -EINVAL; + muic_irq->virq = virq; + + ret = devm_request_threaded_irq(info->dev, virq, NULL, + sm5502_muic_irq_handler, + IRQF_NO_SUSPEND, + muic_irq->name, info); + if (ret) { + dev_err(info->dev, "failed: irq request (IRQ: %d," + " error :%d)\n", muic_irq->irq, ret); + return ret; + } + } + + /* Allocate extcon device */ + info->edev = devm_extcon_dev_allocate(info->dev, sm5502_extcon_cable); + if (IS_ERR(info->edev)) { + dev_err(info->dev, "failed to allocate memory for extcon\n"); + return -ENOMEM; + } + info->edev->name = np->name; + + /* Register extcon device */ + ret = devm_extcon_dev_register(info->dev, info->edev); + if (ret) { + dev_err(info->dev, "failed to register extcon device\n"); + return ret; + } + + /* Initialize SM5502 device and print vendor id and version id */ + sm5502_init_dev_type(info); + + return 0; +} + +static int sm5502_muic_i2c_remove(struct i2c_client *i2c) +{ + struct sm5502_muic_info *info = i2c_get_clientdata(i2c); + + regmap_del_irq_chip(info->irq, info->irq_data); + + return 0; +} + +static struct of_device_id sm5502_dt_match[] = { + { .compatible = "siliconmitus,sm5502-muic" }, + { }, +}; + +#ifdef CONFIG_PM_SLEEP +static int sm5502_muic_suspend(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct sm5502_muic_info *info = i2c_get_clientdata(i2c); + + enable_irq_wake(info->irq); + + return 0; +} + +static int sm5502_muic_resume(struct device *dev) +{ + struct i2c_client *i2c = container_of(dev, struct i2c_client, dev); + struct sm5502_muic_info *info = i2c_get_clientdata(i2c); + + disable_irq_wake(info->irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(sm5502_muic_pm_ops, + sm5502_muic_suspend, sm5502_muic_resume); + +static const struct i2c_device_id sm5502_i2c_id[] = { + { "sm5502", TYPE_SM5502 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sm5502_i2c_id); + +static struct i2c_driver sm5502_muic_i2c_driver = { + .driver = { + .name = "sm5502", + .owner = THIS_MODULE, + .pm = &sm5502_muic_pm_ops, + .of_match_table = sm5502_dt_match, + }, + .probe = sm5022_muic_i2c_probe, + .remove = sm5502_muic_i2c_remove, + .id_table = sm5502_i2c_id, +}; + +static int __init sm5502_muic_i2c_init(void) +{ + return i2c_add_driver(&sm5502_muic_i2c_driver); +} +subsys_initcall(sm5502_muic_i2c_init); + +MODULE_DESCRIPTION("Silicon Mitus SM5502 Extcon driver"); +MODULE_AUTHOR("Chanwoo Choi "); +MODULE_LICENSE("GPL"); diff --git a/include/linux/extcon/sm5502.h b/include/linux/extcon/sm5502.h new file mode 100644 index 000000000000..17bd6550c485 --- /dev/null +++ b/include/linux/extcon/sm5502.h @@ -0,0 +1,264 @@ +/* + * sm5502.h + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __LINUX_EXTCON_SM5502_H +#define __LINUX_EXTCON_SM5502_H + +enum sm5502_types { + TYPE_SM5502, +}; + +/* SM5502 registers */ +enum sm5502_reg { + SM5502_REG_DEVICE_ID = 0x01, + SM5502_REG_CONTROL, + SM5502_REG_INT1, + SM5502_REG_INT2, + SM5502_REG_INTMASK1, + SM5502_REG_INTMASK2, + SM5502_REG_ADC, + SM5502_REG_TIMING_SET1, + SM5502_REG_TIMING_SET2, + SM5502_REG_DEV_TYPE1, + SM5502_REG_DEV_TYPE2, + SM5502_REG_BUTTON1, + SM5502_REG_BUTTON2, + SM5502_REG_CAR_KIT_STATUS, + SM5502_REG_RSVD1, + SM5502_REG_RSVD2, + SM5502_REG_RSVD3, + SM5502_REG_RSVD4, + SM5502_REG_MANUAL_SW1, + SM5502_REG_MANUAL_SW2, + SM5502_REG_DEV_TYPE3, + SM5502_REG_RSVD5, + SM5502_REG_RSVD6, + SM5502_REG_RSVD7, + SM5502_REG_RSVD8, + SM5502_REG_RSVD9, + SM5502_REG_RESET, + SM5502_REG_RSVD10, + SM5502_REG_RESERVED_ID1, + SM5502_REG_RSVD11, + SM5502_REG_RSVD12, + SM5502_REG_RESERVED_ID2, + SM5502_REG_RSVD13, + SM5502_REG_OCP, + SM5502_REG_RSVD14, + SM5502_REG_RSVD15, + SM5502_REG_RSVD16, + SM5502_REG_RSVD17, + SM5502_REG_RSVD18, + SM5502_REG_RSVD19, + SM5502_REG_RSVD20, + SM5502_REG_RSVD21, + SM5502_REG_RSVD22, + SM5502_REG_RSVD23, + SM5502_REG_RSVD24, + SM5502_REG_RSVD25, + SM5502_REG_RSVD26, + SM5502_REG_RSVD27, + SM5502_REG_RSVD28, + SM5502_REG_RSVD29, + SM5502_REG_RSVD30, + SM5502_REG_RSVD31, + SM5502_REG_RSVD32, + SM5502_REG_RSVD33, + SM5502_REG_RSVD34, + SM5502_REG_RSVD35, + SM5502_REG_RSVD36, + SM5502_REG_RESERVED_ID3, + + SM5502_REG_END, +}; + +/* Define SM5502 MASK/SHIFT constant */ +#define SM5502_REG_DEVICE_ID_VENDOR_SHIFT 0 +#define SM5502_REG_DEVICE_ID_VERSION_SHIFT 3 +#define SM5502_REG_DEVICE_ID_VENDOR_MASK (0x3 << SM5502_REG_DEVICE_ID_VENDOR_SHIFT) +#define SM5502_REG_DEVICE_ID_VERSION_MASK (0x1f << SM5502_REG_DEVICE_ID_VERSION_SHIFT) + +#define SM5502_REG_CONTROL_MASK_INT_SHIFT 0 +#define SM5502_REG_CONTROL_WAIT_SHIFT 1 +#define SM5502_REG_CONTROL_MANUAL_SW_SHIFT 2 +#define SM5502_REG_CONTROL_RAW_DATA_SHIFT 3 +#define SM5502_REG_CONTROL_SW_OPEN_SHIFT 4 +#define SM5502_REG_CONTROL_MASK_INT_MASK (0x1 << SM5502_REG_CONTROL_MASK_INT_SHIFT) +#define SM5502_REG_CONTROL_WAIT_MASK (0x1 << SM5502_REG_CONTROL_WAIT_SHIFT) +#define SM5502_REG_CONTROL_MANUAL_SW_MASK (0x1 << SM5502_REG_CONTROL_MANUAL_SW_SHIFT) +#define SM5502_REG_CONTROL_RAW_DATA_MASK (0x1 << SM5502_REG_CONTROL_RAW_DATA_SHIFT) +#define SM5502_REG_CONTROL_SW_OPEN_MASK (0x1 << SM5502_REG_CONTROL_SW_OPEN_SHIFT) + +#define SM5502_REG_INTM1_ATTACH_SHIFT 0 +#define SM5502_REG_INTM1_DETACH_SHIFT 1 +#define SM5502_REG_INTM1_KP_SHIFT 2 +#define SM5502_REG_INTM1_LKP_SHIFT 3 +#define SM5502_REG_INTM1_LKR_SHIFT 4 +#define SM5502_REG_INTM1_OVP_EVENT_SHIFT 5 +#define SM5502_REG_INTM1_OCP_EVENT_SHIFT 6 +#define SM5502_REG_INTM1_OVP_OCP_DIS_SHIFT 7 +#define SM5502_REG_INTM1_ATTACH_MASK (0x1 << SM5502_REG_INTM1_ATTACH_SHIFT) +#define SM5502_REG_INTM1_DETACH_MASK (0x1 << SM5502_REG_INTM1_DETACH_SHIFT) +#define SM5502_REG_INTM1_KP_MASK (0x1 << SM5502_REG_INTM1_KP_SHIFT) +#define SM5502_REG_INTM1_LKP_MASK (0x1 << SM5502_REG_INTM1_LKP_SHIFT) +#define SM5502_REG_INTM1_LKR_MASK (0x1 << SM5502_REG_INTM1_LKR_SHIFT) +#define SM5502_REG_INTM1_OVP_EVENT_MASK (0x1 << SM5502_REG_INTM1_OVP_EVENT_SHIFT) +#define SM5502_REG_INTM1_OCP_EVENT_MASK (0x1 << SM5502_REG_INTM1_OCP_EVENT_SHIFT) +#define SM5502_REG_INTM1_OVP_OCP_DIS_MASK (0x1 << SM5502_REG_INTM1_OVP_OCP_DIS_SHIFT) + +#define SM5502_REG_INTM2_VBUS_DET_SHIFT 0 +#define SM5502_REG_INTM2_REV_ACCE_SHIFT 1 +#define SM5502_REG_INTM2_ADC_CHG_SHIFT 2 +#define SM5502_REG_INTM2_STUCK_KEY_SHIFT 3 +#define SM5502_REG_INTM2_STUCK_KEY_RCV_SHIFT 4 +#define SM5502_REG_INTM2_MHL_SHIFT 5 +#define SM5502_REG_INTM2_VBUS_DET_MASK (0x1 << SM5502_REG_INTM2_VBUS_DET_SHIFT) +#define SM5502_REG_INTM2_REV_ACCE_MASK (0x1 << SM5502_REG_INTM2_REV_ACCE_SHIFT) +#define SM5502_REG_INTM2_ADC_CHG_MASK (0x1 << SM5502_REG_INTM2_ADC_CHG_SHIFT) +#define SM5502_REG_INTM2_STUCK_KEY_MASK (0x1 << SM5502_REG_INTM2_STUCK_KEY_SHIFT) +#define SM5502_REG_INTM2_STUCK_KEY_RCV_MASK (0x1 << SM5502_REG_INTM2_STUCK_KEY_RCV_SHIFT) +#define SM5502_REG_INTM2_MHL_MASK (0x1 << SM5502_REG_INTM2_MHL_SHIFT) + +#define SM5502_REG_ADC_SHIFT 0 +#define SM5502_REG_ADC_MASK (0x1f << SM5502_REG_ADC_SHIFT) + +#define SM5502_REG_TIMING_SET1_KEY_PRESS_SHIFT 4 +#define SM5502_REG_TIMING_SET1_KEY_PRESS_MASK (0xf << SM5502_REG_TIMING_SET1_KEY_PRESS_SHIFT) +#define TIMING_KEY_PRESS_100MS 0x0 +#define TIMING_KEY_PRESS_200MS 0x1 +#define TIMING_KEY_PRESS_300MS 0x2 +#define TIMING_KEY_PRESS_400MS 0x3 +#define TIMING_KEY_PRESS_500MS 0x4 +#define TIMING_KEY_PRESS_600MS 0x5 +#define TIMING_KEY_PRESS_700MS 0x6 +#define TIMING_KEY_PRESS_800MS 0x7 +#define TIMING_KEY_PRESS_900MS 0x8 +#define TIMING_KEY_PRESS_1000MS 0x9 +#define SM5502_REG_TIMING_SET1_ADC_DET_SHIFT 0 +#define SM5502_REG_TIMING_SET1_ADC_DET_MASK (0xf << SM5502_REG_TIMING_SET1_ADC_DET_SHIFT) +#define TIMING_ADC_DET_50MS 0x0 +#define TIMING_ADC_DET_100MS 0x1 +#define TIMING_ADC_DET_150MS 0x2 +#define TIMING_ADC_DET_200MS 0x3 +#define TIMING_ADC_DET_300MS 0x4 +#define TIMING_ADC_DET_400MS 0x5 +#define TIMING_ADC_DET_500MS 0x6 +#define TIMING_ADC_DET_600MS 0x7 +#define TIMING_ADC_DET_700MS 0x8 +#define TIMING_ADC_DET_800MS 0x9 +#define TIMING_ADC_DET_900MS 0xA +#define TIMING_ADC_DET_1000MS 0xB + +#define SM5502_REG_TIMING_SET2_SW_WAIT_SHIFT 4 +#define SM5502_REG_TIMING_SET2_SW_WAIT_MASK (0xf << SM5502_REG_TIMING_SET2_SW_WAIT_SHIFT) +#define TIMING_SW_WAIT_10MS 0x0 +#define TIMING_SW_WAIT_30MS 0x1 +#define TIMING_SW_WAIT_50MS 0x2 +#define TIMING_SW_WAIT_70MS 0x3 +#define TIMING_SW_WAIT_90MS 0x4 +#define TIMING_SW_WAIT_110MS 0x5 +#define TIMING_SW_WAIT_130MS 0x6 +#define TIMING_SW_WAIT_150MS 0x7 +#define TIMING_SW_WAIT_170MS 0x8 +#define TIMING_SW_WAIT_190MS 0x9 +#define TIMING_SW_WAIT_210MS 0xA +#define SM5502_REG_TIMING_SET2_LONG_KEY_SHIFT 0 +#define SM5502_REG_TIMING_SET2_LONG_KEY_MASK (0xf << SM5502_REG_TIMING_SET2_LONG_KEY_SHIFT) +#define TIMING_LONG_KEY_300MS 0x0 +#define TIMING_LONG_KEY_400MS 0x1 +#define TIMING_LONG_KEY_500MS 0x2 +#define TIMING_LONG_KEY_600MS 0x3 +#define TIMING_LONG_KEY_700MS 0x4 +#define TIMING_LONG_KEY_800MS 0x5 +#define TIMING_LONG_KEY_900MS 0x6 +#define TIMING_LONG_KEY_1000MS 0x7 +#define TIMING_LONG_KEY_1100MS 0x8 +#define TIMING_LONG_KEY_1200MS 0x9 +#define TIMING_LONG_KEY_1300MS 0xA +#define TIMING_LONG_KEY_1400MS 0xB +#define TIMING_LONG_KEY_1500MS 0xC + +#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE1_SHIFT 0 +#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE2_SHIFT 1 +#define SM5502_REG_DEV_TYPE1_USB_SDP_SHIFT 2 +#define SM5502_REG_DEV_TYPE1_UART_SHIFT 3 +#define SM5502_REG_DEV_TYPE1_CAR_KIT_CHARGER_SHIFT 4 +#define SM5502_REG_DEV_TYPE1_USB_CHG_SHIFT 5 +#define SM5502_REG_DEV_TYPE1_DEDICATED_CHG_SHIFT 6 +#define SM5502_REG_DEV_TYPE1_USB_OTG_SHIFT 7 +#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE1_MASK (0x1 << SM5502_REG_DEV_TYPE1_AUDIO_TYPE1_SHIFT) +#define SM5502_REG_DEV_TYPE1_AUDIO_TYPE1__MASK (0x1 << SM5502_REG_DEV_TYPE1_AUDIO_TYPE2_SHIFT) +#define SM5502_REG_DEV_TYPE1_USB_SDP_MASK (0x1 << SM5502_REG_DEV_TYPE1_USB_SDP_SHIFT) +#define SM5502_REG_DEV_TYPE1_UART_MASK (0x1 << SM5502_REG_DEV_TYPE1_UART_SHIFT) +#define SM5502_REG_DEV_TYPE1_CAR_KIT_CHARGER_MASK (0x1 << SM5502_REG_DEV_TYPE1_CAR_KIT_CHARGER_SHIFT) +#define SM5502_REG_DEV_TYPE1_USB_CHG_MASK (0x1 << SM5502_REG_DEV_TYPE1_USB_CHG_SHIFT) +#define SM5502_REG_DEV_TYPE1_DEDICATED_CHG_MASK (0x1 << SM5502_REG_DEV_TYPE1_DEDICATED_CHG_SHIFT) +#define SM5502_REG_DEV_TYPE1_USB_OTG_MASK (0x1 << SM5502_REG_DEV_TYPE1_USB_OTG_SHIFT) + +#define SM5502_REG_DEV_TYPE2_JIG_USB_ON_SHIFT 0 +#define SM5502_REG_DEV_TYPE2_JIG_USB_OFF_SHIFT 1 +#define SM5502_REG_DEV_TYPE2_JIG_UART_ON_SHIFT 2 +#define SM5502_REG_DEV_TYPE2_JIG_UART_OFF_SHIFT 3 +#define SM5502_REG_DEV_TYPE2_PPD_SHIFT 4 +#define SM5502_REG_DEV_TYPE2_TTY_SHIFT 5 +#define SM5502_REG_DEV_TYPE2_AV_CABLE_SHIFT 6 +#define SM5502_REG_DEV_TYPE2_JIG_USB_ON_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_USB_ON_SHIFT) +#define SM5502_REG_DEV_TYPE2_JIG_USB_OFF_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_USB_OFF_SHIFT) +#define SM5502_REG_DEV_TYPE2_JIG_UART_ON_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_UART_ON_SHIFT) +#define SM5502_REG_DEV_TYPE2_JIG_UART_OFF_MASK (0x1 << SM5502_REG_DEV_TYPE2_JIG_UART_OFF_SHIFT) +#define SM5502_REG_DEV_TYPE2_PPD_MASK (0x1 << SM5502_REG_DEV_TYPE2_PPD_SHIFT) +#define SM5502_REG_DEV_TYPE2_TTY_MASK (0x1 << SM5502_REG_DEV_TYPE2_TTY_SHIFT) +#define SM5502_REG_DEV_TYPE2_AV_CABLE_MASK (0x1 << SM5502_REG_DEV_TYPE2_AV_CABLE_SHIFT) + +/* SM5502 Interrupts */ +enum sm5502_irq { + /* INT1 */ + SM5502_IRQ_INT1_ATTACH, + SM5502_IRQ_INT1_DETACH, + SM5502_IRQ_INT1_KP, + SM5502_IRQ_INT1_LKP, + SM5502_IRQ_INT1_LKR, + SM5502_IRQ_INT1_OVP_EVENT, + SM5502_IRQ_INT1_OCP_EVENT, + SM5502_IRQ_INT1_OVP_OCP_DIS, + + /* INT2 */ + SM5502_IRQ_INT2_VBUS_DET, + SM5502_IRQ_INT2_REV_ACCE, + SM5502_IRQ_INT2_ADC_CHG, + SM5502_IRQ_INT2_STUCK_KEY, + SM5502_IRQ_INT2_STUCK_KEY_RCV, + SM5502_IRQ_INT2_MHL, + + SM5502_IRQ_NUM, +}; + +#define SM5502_IRQ_INT1_ATTACH_MASK BIT(0) +#define SM5502_IRQ_INT1_DETACH_MASK BIT(1) +#define SM5502_IRQ_INT1_KP_MASK BIT(2) +#define SM5502_IRQ_INT1_LKP_MASK BIT(3) +#define SM5502_IRQ_INT1_LKR_MASK BIT(4) +#define SM5502_IRQ_INT1_OVP_EVENT_MASK BIT(5) +#define SM5502_IRQ_INT1_OCP_EVENT_MASK BIT(6) +#define SM5502_IRQ_INT1_OVP_OCP_DIS_MASK BIT(7) +#define SM5502_IRQ_INT2_VBUS_DET_MASK BIT(0) +#define SM5502_IRQ_INT2_REV_ACCE_MASK BIT(1) +#define SM5502_IRQ_INT2_ADC_CHG_MASK BIT(2) +#define SM5502_IRQ_INT2_STUCK_KEY_MASK BIT(3) +#define SM5502_IRQ_INT2_STUCK_KEY_RCV_MASK BIT(4) +#define SM5502_IRQ_INT2_MHL_MASK BIT(5) + +#endif /* __LINUX_EXTCON_SM5502_H */ -- cgit v1.2.3 From e1954452f500cb21c09ea401f6f431ab55b35ba3 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 28 May 2014 14:21:55 +0900 Subject: extcon: sm5502: Detect cable state after completing platform booting This patch detect whether cable is connected or not and the cable type after completing kernel/platform booting using system_power_efficient_wq. Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-sm5502.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/extcon/extcon-sm5502.c b/drivers/extcon/extcon-sm5502.c index 9f318c222b89..a32d40f97ff0 100644 --- a/drivers/extcon/extcon-sm5502.c +++ b/drivers/extcon/extcon-sm5502.c @@ -28,6 +28,8 @@ #include #include +#define DELAY_MS_DEFAULT 17000 /* unit: millisecond */ + struct muic_irq { unsigned int irq; const char *name; @@ -59,6 +61,14 @@ struct sm5502_muic_info { unsigned int num_reg_data; struct mutex mutex; + + /* + * Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + struct delayed_work wq_detcable; }; /* Default value of SM5502 register to bring up MUIC device. */ @@ -341,6 +351,8 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info, case SM5502_MUIC_ADC_OPEN_USB_OTG: idx = EXTCON_CABLE_USB_HOST; break; + case SM5502_MUIC_ADC_GROUND: + break; default: dev_dbg(info->dev, "cannot handle this cable_type (0x%x)\n", cable_type); @@ -433,6 +445,18 @@ static irqreturn_t sm5502_muic_irq_handler(int irq, void *data) return IRQ_HANDLED; } +static void sm5502_muic_detect_cable_wq(struct work_struct *work) +{ + struct sm5502_muic_info *info = container_of(to_delayed_work(work), + struct sm5502_muic_info, wq_detcable); + int ret; + + /* Notify the state of connector cable or not */ + ret = sm5502_muic_cable_handler(info, true); + if (ret < 0) + dev_warn(info->dev, "failed to detect cable state\n"); +} + static void sm5502_init_dev_type(struct sm5502_muic_info *info) { unsigned int reg_data, vendor_id, version_id; @@ -546,6 +570,18 @@ static int sm5022_muic_i2c_probe(struct i2c_client *i2c, return ret; } + /* + * Detect accessory after completing the initialization of platform + * + * - Use delayed workqueue to detect cable state and then + * notify cable state to notifiee/platform through uevent. + * After completing the booting of platform, the extcon provider + * driver should notify cable state to upper layer. + */ + INIT_DELAYED_WORK(&info->wq_detcable, sm5502_muic_detect_cable_wq); + queue_delayed_work(system_power_efficient_wq, &info->wq_detcable, + msecs_to_jiffies(DELAY_MS_DEFAULT)); + /* Initialize SM5502 device and print vendor id and version id */ sm5502_init_dev_type(info); -- cgit v1.2.3 From a75fed2ee6c187ab32b1cb01882c1032c4c9e4a8 Mon Sep 17 00:00:00 2001 From: Chanwoo Choi Date: Wed, 28 May 2014 15:35:29 +0900 Subject: extcon: sm5502: Change internal hardware switch according to cable type This patch changes internal hardware DP_CON/DM_CON switch according to cable type. The SM5502 MUIC device can set hardware switch as following: - OPEN (not connected state) / USB / UART / AUDIO Also, this patch set VBUSIN switch according to cable type. Signed-off-by: Chanwoo Choi --- drivers/extcon/extcon-sm5502.c | 78 +++++++++++++++++++++++++++++++++++++++--- include/linux/extcon/sm5502.h | 23 +++++++++++++ 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/drivers/extcon/extcon-sm5502.c b/drivers/extcon/extcon-sm5502.c index a32d40f97ff0..560d7dccec7b 100644 --- a/drivers/extcon/extcon-sm5502.c +++ b/drivers/extcon/extcon-sm5502.c @@ -228,6 +228,61 @@ static const struct regmap_config sm5502_muic_regmap_config = { .max_register = SM5502_REG_END, }; +/* Change DM_CON/DP_CON/VBUSIN switch according to cable type */ +static int sm5502_muic_set_path(struct sm5502_muic_info *info, + unsigned int con_sw, unsigned int vbus_sw, + bool attached) +{ + int ret; + + if (!attached) { + con_sw = DM_DP_SWITCH_OPEN; + vbus_sw = VBUSIN_SWITCH_OPEN; + } + + switch (con_sw) { + case DM_DP_SWITCH_OPEN: + case DM_DP_SWITCH_USB: + case DM_DP_SWITCH_AUDIO: + case DM_DP_SWITCH_UART: + ret = regmap_update_bits(info->regmap, SM5502_REG_MANUAL_SW1, + SM5502_REG_MANUAL_SW1_DP_MASK | + SM5502_REG_MANUAL_SW1_DM_MASK, + con_sw); + if (ret < 0) { + dev_err(info->dev, + "cannot update DM_CON/DP_CON switch\n"); + return ret; + } + break; + default: + dev_err(info->dev, "Unknown DM_CON/DP_CON switch type (%d)\n", + con_sw); + return -EINVAL; + }; + + switch (vbus_sw) { + case VBUSIN_SWITCH_OPEN: + case VBUSIN_SWITCH_VBUSOUT: + case VBUSIN_SWITCH_MIC: + case VBUSIN_SWITCH_VBUSOUT_WITH_USB: + ret = regmap_update_bits(info->regmap, SM5502_REG_MANUAL_SW1, + SM5502_REG_MANUAL_SW1_VBUSIN_MASK, + vbus_sw); + if (ret < 0) { + dev_err(info->dev, + "cannot update VBUSIN switch\n"); + return ret; + } + break; + default: + dev_err(info->dev, "Unknown VBUS switch type (%d)\n", vbus_sw); + return -EINVAL; + }; + + return 0; +} + /* Return cable type of attached or detached accessories */ static unsigned int sm5502_muic_get_cable_type(struct sm5502_muic_info *info) { @@ -329,7 +384,10 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info, static unsigned int prev_cable_type = SM5502_MUIC_ADC_GROUND; const char **cable_names = info->edev->supported_cable; unsigned int cable_type = SM5502_MUIC_ADC_GROUND; + unsigned int con_sw = DM_DP_SWITCH_OPEN; + unsigned int vbus_sw = VBUSIN_SWITCH_OPEN; unsigned int idx = 0; + int ret; if (!cable_names) return 0; @@ -343,15 +401,19 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info, switch (cable_type) { case SM5502_MUIC_ADC_OPEN_USB: - idx = EXTCON_CABLE_USB; + idx = EXTCON_CABLE_USB; + con_sw = DM_DP_SWITCH_USB; + vbus_sw = VBUSIN_SWITCH_VBUSOUT_WITH_USB; break; case SM5502_MUIC_ADC_OPEN_TA: - idx = EXTCON_CABLE_TA; + idx = EXTCON_CABLE_TA; + con_sw = DM_DP_SWITCH_OPEN; + vbus_sw = VBUSIN_SWITCH_VBUSOUT; break; case SM5502_MUIC_ADC_OPEN_USB_OTG: - idx = EXTCON_CABLE_USB_HOST; - break; - case SM5502_MUIC_ADC_GROUND: + idx = EXTCON_CABLE_USB_HOST; + con_sw = DM_DP_SWITCH_USB; + vbus_sw = VBUSIN_SWITCH_OPEN; break; default: dev_dbg(info->dev, @@ -359,6 +421,12 @@ static int sm5502_muic_cable_handler(struct sm5502_muic_info *info, return 0; }; + /* Change internal hardware path(DM_CON/DP_CON, VBUSIN) */ + ret = sm5502_muic_set_path(info, con_sw, vbus_sw, attached); + if (ret < 0) + return ret; + + /* Change the state of external accessory */ extcon_set_cable_state(info->edev, cable_names[idx], attached); return 0; diff --git a/include/linux/extcon/sm5502.h b/include/linux/extcon/sm5502.h index 17bd6550c485..030526bf8d79 100644 --- a/include/linux/extcon/sm5502.h +++ b/include/linux/extcon/sm5502.h @@ -223,6 +223,29 @@ enum sm5502_reg { #define SM5502_REG_DEV_TYPE2_TTY_MASK (0x1 << SM5502_REG_DEV_TYPE2_TTY_SHIFT) #define SM5502_REG_DEV_TYPE2_AV_CABLE_MASK (0x1 << SM5502_REG_DEV_TYPE2_AV_CABLE_SHIFT) +#define SM5502_REG_MANUAL_SW1_VBUSIN_SHIFT 0 +#define SM5502_REG_MANUAL_SW1_DP_SHIFT 2 +#define SM5502_REG_MANUAL_SW1_DM_SHIFT 5 +#define SM5502_REG_MANUAL_SW1_VBUSIN_MASK (0x3 << SM5502_REG_MANUAL_SW1_VBUSIN_SHIFT) +#define SM5502_REG_MANUAL_SW1_DP_MASK (0x7 << SM5502_REG_MANUAL_SW1_DP_SHIFT) +#define SM5502_REG_MANUAL_SW1_DM_MASK (0x7 << SM5502_REG_MANUAL_SW1_DM_SHIFT) +#define VBUSIN_SWITCH_OPEN 0x0 +#define VBUSIN_SWITCH_VBUSOUT 0x1 +#define VBUSIN_SWITCH_MIC 0x2 +#define VBUSIN_SWITCH_VBUSOUT_WITH_USB 0x3 +#define DM_DP_CON_SWITCH_OPEN 0x0 +#define DM_DP_CON_SWITCH_USB 0x1 +#define DM_DP_CON_SWITCH_AUDIO 0x2 +#define DM_DP_CON_SWITCH_UART 0x3 +#define DM_DP_SWITCH_OPEN ((DM_DP_CON_SWITCH_OPEN < Date: Fri, 23 May 2014 15:35:22 +0900 Subject: dt-bindings: extcon: Add support for SM5502 MUIC device This patch add documentation for binding of SM5502 MUIC (Micro-USB Interface Controller) device which is using EXTCON subsystem. The SM5502 MUIC device can detect various external accessories when external accessories is attached or detached. Signed-off-by: Chanwoo Choi --- .../devicetree/bindings/extcon/extcon-sm5502.txt | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Documentation/devicetree/bindings/extcon/extcon-sm5502.txt diff --git a/Documentation/devicetree/bindings/extcon/extcon-sm5502.txt b/Documentation/devicetree/bindings/extcon/extcon-sm5502.txt new file mode 100644 index 000000000000..4ecda224955f --- /dev/null +++ b/Documentation/devicetree/bindings/extcon/extcon-sm5502.txt @@ -0,0 +1,23 @@ + +* SM5502 MUIC (Micro-USB Interface Controller) device + +The Silicon Mitus SM5502 is a MUIC (Micro-USB Interface Controller) device +which can detect the state of external accessory when external accessory is +attached or detached and button is pressed or released. It is interfaced to +the host controller using an I2C interface. + +Required properties: +- compatible: Should be "siliconmitus,sm5502-muic" +- reg: Specifies the I2C slave address of the MUIC block. It should be 0x25 +- interrupt-parent: Specifies the phandle of the interrupt controller to which + the interrupts from sm5502 are delivered to. +- interrupts: Interrupt specifiers for detection interrupt sources. + +Example: + + sm5502@25 { + compatible = "siliconmitus,sm5502-muic"; + interrupt-parent = <&gpx1>; + interrupts = <5 0>; + reg = <0x25>; + }; -- cgit v1.2.3 From 4dcc2ab3f869c5ecdcf4edfe5308767121f8a136 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Tue, 22 Jul 2014 13:08:38 +0200 Subject: drivers/misc/ti-st: Load firmware from ti-connectivity directory. Looks like the default location for TI firmware is inside the ti-connectivity directory, to be coherent with other firmware request used by TI drivers, load the TIInit firmware from this directory instead of /lib/firmware directly. Signed-off-by: Enric Balletbo i Serra Signed-off-by: Greg Kroah-Hartman --- drivers/misc/ti-st/st_kim.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/misc/ti-st/st_kim.c b/drivers/misc/ti-st/st_kim.c index 45007be7cfb7..21c2337bad68 100644 --- a/drivers/misc/ti-st/st_kim.c +++ b/drivers/misc/ti-st/st_kim.c @@ -244,7 +244,8 @@ static long read_local_version(struct kim_data_s *kim_gdata, char *bts_scr_name) if (version & 0x8000) maj_ver |= 0x0008; - sprintf(bts_scr_name, "TIInit_%d.%d.%d.bts", chip, maj_ver, min_ver); + sprintf(bts_scr_name, "ti-connectivity/TIInit_%d.%d.%d.bts", + chip, maj_ver, min_ver); /* to be accessed later via sysfs entry */ kim_gdata->version.full = version; @@ -287,7 +288,7 @@ static long download_firmware(struct kim_data_s *kim_gdata) long len = 0; unsigned char *ptr = NULL; unsigned char *action_ptr = NULL; - unsigned char bts_scr_name[30] = { 0 }; /* 30 char long bts scr name? */ + unsigned char bts_scr_name[40] = { 0 }; /* 40 char long bts scr name? */ int wr_room_space; int cmd_size; unsigned long timeout; -- cgit v1.2.3 From ee5311420d03eccee02e447e698d2fda6c25583d Mon Sep 17 00:00:00 2001 From: Jean-Michel Hautbois Date: Thu, 24 Jul 2014 10:53:00 +0200 Subject: Lattice ECP3 FPGA: Correct endianness This code corrects endianness and avoids a sparse error. Tested with Lattice ECP3-35 with Freescale i.MX6. It also sends uevent in order to load it. Signed-off-by: Jean-Michel Hautbois Signed-off-by: Greg Kroah-Hartman --- drivers/misc/lattice-ecp3-config.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/misc/lattice-ecp3-config.c b/drivers/misc/lattice-ecp3-config.c index 0a1565e63c71..7ffdb589841e 100644 --- a/drivers/misc/lattice-ecp3-config.c +++ b/drivers/misc/lattice-ecp3-config.c @@ -15,6 +15,7 @@ #include #include #include +#include #define FIRMWARE_NAME "lattice-ecp3.bit" @@ -91,8 +92,8 @@ static void firmware_load(const struct firmware *fw, void *context) /* Trying to speak with the FPGA via SPI... */ txbuf[0] = FPGA_CMD_READ_ID; ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); - dev_dbg(&spi->dev, "FPGA JTAG ID=%08x\n", *(u32 *)&rxbuf[4]); - jedec_id = *(u32 *)&rxbuf[4]; + jedec_id = get_unaligned_be32(&rxbuf[4]); + dev_dbg(&spi->dev, "FPGA JTAG ID=%08x\n", jedec_id); for (i = 0; i < ARRAY_SIZE(ecp3_dev); i++) { if (jedec_id == ecp3_dev[i].jedec_id) @@ -109,7 +110,8 @@ static void firmware_load(const struct firmware *fw, void *context) txbuf[0] = FPGA_CMD_READ_STATUS; ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); - dev_dbg(&spi->dev, "FPGA Status=%08x\n", *(u32 *)&rxbuf[4]); + status = get_unaligned_be32(&rxbuf[4]); + dev_dbg(&spi->dev, "FPGA Status=%08x\n", status); buffer = kzalloc(fw->size + 8, GFP_KERNEL); if (!buffer) { @@ -141,7 +143,7 @@ static void firmware_load(const struct firmware *fw, void *context) for (i = 0; i < FPGA_CLEAR_LOOP_COUNT; i++) { txbuf[0] = FPGA_CMD_READ_STATUS; ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); - status = *(u32 *)&rxbuf[4]; + status = get_unaligned_be32(&rxbuf[4]); if (status == FPGA_STATUS_CLEARED) break; @@ -164,8 +166,8 @@ static void firmware_load(const struct firmware *fw, void *context) txbuf[0] = FPGA_CMD_READ_STATUS; ret = spi_write_then_read(spi, txbuf, 8, rxbuf, rx_len); - dev_dbg(&spi->dev, "FPGA Status=%08x\n", *(u32 *)&rxbuf[4]); - status = *(u32 *)&rxbuf[4]; + status = get_unaligned_be32(&rxbuf[4]); + dev_dbg(&spi->dev, "FPGA Status=%08x\n", status); /* Check result */ if (status & FPGA_STATUS_DONE) @@ -196,7 +198,7 @@ static int lattice_ecp3_probe(struct spi_device *spi) spi_set_drvdata(spi, data); init_completion(&data->fw_loaded); - err = request_firmware_nowait(THIS_MODULE, FW_ACTION_NOHOTPLUG, + err = request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG, FIRMWARE_NAME, &spi->dev, GFP_KERNEL, spi, firmware_load); if (err) { -- cgit v1.2.3 From 7b9d1f0b7a18b86db0ac1de628fa91c0994fefbe Mon Sep 17 00:00:00 2001 From: Himangi Saraogi Date: Thu, 24 Jul 2014 02:45:40 +0530 Subject: misc: bh1780: Introduce the use of devm_kzalloc This patch introduces the use of devm_kzalloc and does away with the kfrees in the probe and remove functions. A label and the kfree being called on the return path of failure on i2c_check_functionality, which is completely unnecessary and removed. The NULL assignment of ddata is no longer required is also done away with. Signed-off-by: Himangi Saraogi Acked-by: Julia Lawall Signed-off-by: Greg Kroah-Hartman --- drivers/misc/bh1780gli.c | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/drivers/misc/bh1780gli.c b/drivers/misc/bh1780gli.c index 48ea33d15a79..4c4a59b25537 100644 --- a/drivers/misc/bh1780gli.c +++ b/drivers/misc/bh1780gli.c @@ -149,50 +149,35 @@ static int bh1780_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret; - struct bh1780_data *ddata = NULL; + struct bh1780_data *ddata; struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); - if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) { - ret = -EIO; - goto err_op_failed; - } + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + return -EIO; - ddata = kzalloc(sizeof(struct bh1780_data), GFP_KERNEL); - if (ddata == NULL) { - ret = -ENOMEM; - goto err_op_failed; - } + ddata = devm_kzalloc(&client->dev, sizeof(struct bh1780_data), + GFP_KERNEL); + if (ddata == NULL) + return -ENOMEM; ddata->client = client; i2c_set_clientdata(client, ddata); ret = bh1780_read(ddata, BH1780_REG_PARTID, "PART ID"); if (ret < 0) - goto err_op_failed; + return ret; dev_info(&client->dev, "Ambient Light Sensor, Rev : %d\n", (ret & BH1780_REVMASK)); mutex_init(&ddata->lock); - ret = sysfs_create_group(&client->dev.kobj, &bh1780_attr_group); - if (ret) - goto err_op_failed; - - return 0; - -err_op_failed: - kfree(ddata); - return ret; + return sysfs_create_group(&client->dev.kobj, &bh1780_attr_group); } static int bh1780_remove(struct i2c_client *client) { - struct bh1780_data *ddata; - - ddata = i2c_get_clientdata(client); sysfs_remove_group(&client->dev.kobj, &bh1780_attr_group); - kfree(ddata); return 0; } -- cgit v1.2.3