Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e7ed484 authored by Subhash Jadavani's avatar Subhash Jadavani
Browse files

scsi: ufs: add hot plug support for removable UFS cards



Now that we can have the removable UFS cards which can be plugged in/out
at runtime, we need to add the support for gpio based card
insertion/removal event detection support. But this is bit complicated
as current platforms have the combo card slot which can either take UFS
or SD card at one time and there is a single GPIO assigned for card detect
event. This means we need some module which takes the responsibility of
controlling (configuration, interrupt handling) this shared card detection
GPIO and pass card detection event to both UFS and SD card drivers so they
can probe to see what card is inserted. Kernel already have the extcon-gpio
module which can be used as gpio control module and can pass the card
detection event to both UFS & SD card drivers. So UFS driver registers the
notification callback with extcon driver and tries to detect/remove the UFS
card.

Change-Id: Id65156289c2a722ac4a501020fdbfe6f97a354a9
Signed-off-by: default avatarSubhash Jadavani <subhashj@codeaurora.org>
parent efc62c2e
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -70,6 +70,7 @@ Optional properties:
			  2: 38.4 MHz
			  3: 52 MHz
			  Defaults to 26 MHz if not specified.
- extcon:       phandle to external connector (Refer Documentation/devicetree/bindings/extcon/extcon-gpio.txt for more details).

Note: If above properties are not defined it can be assumed that the supply
regulators or clocks are always on.
+2 −0
Original line number Diff line number Diff line
@@ -83,6 +83,8 @@ config SCSI_UFS_QCOM
	tristate "QCOM specific hooks to UFS controller platform driver"
	depends on SCSI_UFSHCD_PLATFORM && ARCH_QCOM
	select PHY_QCOM_UFS
	select EXTCON
	select EXTCON_GPIO
	help
	  This selects the QCOM specific additions to UFSHCD platform driver.
	  UFS host on QCOM needs some vendor specific configuration before
+17 −0
Original line number Diff line number Diff line
@@ -327,6 +327,20 @@ static int ufshcd_parse_pinctrl_info(struct ufs_hba *hba)
	return ret;
}

static int ufshcd_parse_extcon_info(struct ufs_hba *hba)
{
	struct extcon_dev *extcon;

	extcon = extcon_get_edev_by_phandle(hba->dev, 0);
	if (IS_ERR(extcon) && PTR_ERR(extcon) != -ENODEV)
		return PTR_ERR(extcon);

	if (!IS_ERR(extcon))
		hba->extcon = extcon;

	return 0;
}

#ifdef CONFIG_SMP
/**
 * ufshcd_pltfrm_suspend - suspend power management function
@@ -449,6 +463,9 @@ int ufshcd_pltfrm_init(struct platform_device *pdev,
	ufshcd_parse_pm_levels(hba);
	ufshcd_parse_gear_limits(hba);
	ufshcd_parse_cmd_timeout(hba);
	err = ufshcd_parse_extcon_info(hba);
	if (err)
		goto dealloc_host;

	if (!dev->dma_mask)
		dev->dma_mask = &dev->coherent_dma_mask;
+95 −38
Original line number Diff line number Diff line
@@ -6965,6 +6965,23 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba *hba)
	return err;
}

static int ufshcd_detect_device(struct ufs_hba *hba)
{
	int err = 0;

	err = ufshcd_vops_full_reset(hba);
	if (err)
		dev_warn(hba->dev, "%s: full reset returned %d\n",
			 __func__, err);

	err = ufshcd_reset_device(hba);
	if (err)
		dev_warn(hba->dev, "%s: device reset failed. err %d\n",
			 __func__, err);

	return ufshcd_host_reset_and_restore(hba);
}

/**
 * ufshcd_reset_and_restore - reset and re-initialize host/device
 * @hba: per-adapter instance
@@ -6981,25 +6998,9 @@ static int ufshcd_reset_and_restore(struct ufs_hba *hba)
	int retries = MAX_HOST_RESET_RETRIES;

	do {
		err = ufshcd_vops_full_reset(hba);
		if (err)
			dev_warn(hba->dev, "%s: full reset returned %d\n",
				 __func__, err);

		err = ufshcd_reset_device(hba);
		if (err)
			dev_warn(hba->dev, "%s: device reset failed. err %d\n",
				 __func__, err);

		err = ufshcd_host_reset_and_restore(hba);
		err = ufshcd_detect_device(hba);
	} while (err && --retries);

	/*
	 * There is no point proceeding even after failing
	 * to recover after multiple retries.
	 */
	if (err)
		BUG();
	/*
	 * After reset the door-bell might be cleared, complete
	 * outstanding requests in s/w here.
@@ -7703,10 +7704,8 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
	 * If we failed to initialize the device or the device is not
	 * present, turn off the power/clocks etc.
	 */
	if (ret && !ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress) {
	if (ret && !ufshcd_eh_in_progress(hba) && !hba->pm_op_in_progress)
		pm_runtime_put_sync(hba->dev);
		ufshcd_hba_exit(hba);
	}

	trace_ufshcd_init(dev_name(hba->dev), ret,
		ktime_to_us(ktime_sub(ktime_get(), start)),
@@ -7714,6 +7713,70 @@ static int ufshcd_probe_hba(struct ufs_hba *hba)
	return ret;
}

static void ufshcd_card_detect_handler(struct work_struct *work)
{
	struct ufs_hba *hba;

	hba = container_of(work, struct ufs_hba, card_detect_work);
	if (hba->card_detect_event &&
	    (hba->ufshcd_state != UFSHCD_STATE_OPERATIONAL)) {
		dev_dbg(hba->dev, "%s: card detect notification received\n",
			 __func__);
		pm_runtime_get_sync(hba->dev);
		ufshcd_detect_device(hba);
		pm_runtime_put_sync(hba->dev);
	} else {
		dev_dbg(hba->dev, "%s: card removed notification received\n",
			 __func__);
		/* TODO: remove the scsi device instances */
	}
}

static int ufshcd_card_detect_notifier(struct notifier_block *nb,
				       unsigned long event, void *ptr)
{
	struct ufs_hba *hba = container_of(nb, struct ufs_hba, card_detect_nb);

	hba->card_detect_event = event;
	schedule_work(&hba->card_detect_work);

	return NOTIFY_DONE;
}

static int ufshcd_extcon_register(struct ufs_hba *hba)
{
	int ret;

	if (!hba->extcon)
		return 0;

	hba->card_detect_nb.notifier_call = ufshcd_card_detect_notifier;
	ret = extcon_register_notifier(hba->extcon,
				       EXTCON_MECHANICAL,
				       &hba->card_detect_nb);
	if (ret)
		dev_err(hba->dev, "%s: extcon_register_notifier() failed, ret %d\n",
			__func__, ret);

	return ret;
}

static int ufshcd_extcon_unregister(struct ufs_hba *hba)
{
	int ret;

	if (!hba->extcon)
		return 0;

	ret = extcon_unregister_notifier(hba->extcon, EXTCON_MECHANICAL,
					 &hba->card_detect_nb);
	if (ret)
		dev_err(hba->dev, "%s: extcon_unregister_notifier() failed, ret %d\n",
			__func__, ret);

	return ret;
}

/**
 * ufshcd_async_scan - asynchronous execution for probing hba
 * @data: data pointer to pass to this function
@@ -7730,6 +7793,8 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie)
	ufshcd_hold_all(hba);
	ufshcd_probe_hba(hba);
	ufshcd_release_all(hba);

	ufshcd_extcon_register(hba);
}

/**
@@ -8438,21 +8503,10 @@ static int ufshcd_variant_hba_init(struct ufs_hba *hba)
		goto out;

	err = ufshcd_vops_init(hba);
	if (err)
		goto out;

	err = ufshcd_vops_setup_regulators(hba, true);
	if (err)
		goto out_exit;

	goto out;

out_exit:
	ufshcd_vops_exit(hba);
out:
	if (err)
		dev_err(hba->dev, "%s: variant %s init failed err %d\n",
			__func__, ufshcd_get_var_name(hba), err);
out:
	return err;
}

@@ -8461,8 +8515,6 @@ static void ufshcd_variant_hba_exit(struct ufs_hba *hba)
	if (!hba->var || !hba->var->vops)
		return;

	ufshcd_vops_setup_regulators(hba, false);

	ufshcd_vops_exit(hba);
}

@@ -8521,6 +8573,7 @@ static int ufshcd_hba_init(struct ufs_hba *hba)
static void ufshcd_hba_exit(struct ufs_hba *hba)
{
	if (hba->is_powered) {
		ufshcd_extcon_unregister(hba);
		ufshcd_variant_hba_exit(hba);
		ufshcd_setup_vreg(hba, false);
		if (ufshcd_is_clkscaling_supported(hba)) {
@@ -8825,10 +8878,8 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
		goto enable_gating;

	/* UFS device & link must be active before we enter in this function */
	if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba)) {
		ret = -EINVAL;
		goto enable_gating;
	}
	if (!ufshcd_is_ufs_dev_active(hba) || !ufshcd_is_link_active(hba))
		goto set_vreg_lpm;

	if (ufshcd_is_runtime_pm(pm_op)) {
		if (ufshcd_can_autobkops_during_suspend(hba)) {
@@ -8864,6 +8915,7 @@ static int ufshcd_suspend(struct ufs_hba *hba, enum ufs_pm_op pm_op)
	    ufshcd_is_hibern8_on_idle_allowed(hba))
		hba->hibern8_on_idle.state = HIBERN8_ENTERED;

set_vreg_lpm:
	ufshcd_vreg_set_lpm(hba);

disable_clks:
@@ -8966,6 +9018,9 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
	if (ret)
		goto disable_vreg;

	if (ufshcd_is_link_off(hba))
		goto skip_dev_ops;

	if (ufshcd_is_link_hibern8(hba)) {
		ret = ufshcd_uic_hibern8_exit(hba);
		if (!ret) {
@@ -9013,6 +9068,7 @@ static int ufshcd_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
	if (hba->clk_scaling.is_allowed)
		ufshcd_resume_clkscaling(hba);

skip_dev_ops:
	/* Schedule clock gating in case of no access to UFS device yet */
	ufshcd_release_all(hba);
	goto out;
@@ -10074,6 +10130,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq)
	/* Initialize work queues */
	INIT_WORK(&hba->eh_work, ufshcd_err_handler);
	INIT_WORK(&hba->eeh_work, ufshcd_exception_event_handler);
	INIT_WORK(&hba->card_detect_work, ufshcd_card_detect_handler);

	/* Initialize UIC command mutex */
	mutex_init(&hba->uic_cmd_mutex);
+10 −0
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@
#include <linux/completion.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/extcon.h>
#include "unipro.h"

#include <asm/irq.h>
@@ -724,6 +725,10 @@ struct ufshcd_cmd_log {
 * @ufs_stats: ufshcd statistics to be used via debugfs
 * @debugfs_files: debugfs files associated with the ufs stats
 * @ufshcd_dbg_print: Bitmask for enabling debug prints
 * @extcon: pointer to external connector device
 * @card_detect_nb: card detector notifier registered with @extcon
 * @card_detect_work: work to exectute the card detect function
 * @card_detect_event: card detect event, 0 = removed, 1 = inserted
 * @vreg_info: UFS device voltage regulator information
 * @clk_list_head: UFS host controller clocks list node head
 * @pwr_info: holds current power mode
@@ -896,6 +901,11 @@ struct ufs_hba {
	/* Bitmask for enabling debug prints */
	u32 ufshcd_dbg_print;

	struct extcon_dev *extcon;
	struct notifier_block card_detect_nb;
	struct work_struct card_detect_work;
	unsigned long card_detect_event;

	struct ufs_pa_layer_attr pwr_info;
	struct ufs_pwr_mode_info max_pwr_info;