diff mbox series

mmc: core: Allow speed modes to be adjusted via module param

Message ID 20220623080009.1775574-1-vincent.whitchurch@axis.com
State New
Headers show
Series mmc: core: Allow speed modes to be adjusted via module param | expand

Commit Message

Vincent Whitchurch June 23, 2022, 8 a.m. UTC
During board verification, there is a need to test the various supported
eMMC/SD speed modes.  However, since the framework chooses the best mode
supported by the card and the host controller's caps, this currently
necessitates changing the devicetree for every iteration.

To make changing the modes easier, allow the various host controller
capabilities to be cleared via a module parameter.  (A per-controller
debugfs wouldn't work since the controller needs to be re-probed to
trigger re-init of cards.  A module parameter is used instead of a
global debugfs to allow this to be also set via the kernel command
line.)

The values to be written are the raw MMC_CAP* values from
include/linux/mmc/host.h.  This is rather low-level, and these defines
are not guaranteed to be stable, but it is perhaps good enough for the
indented use case.  A warning is emitted when the caps clearing is in
effect.

Example of use:

 # grep timing /sys/kernel/debug/mmc0/ios
 timing spec:	9 (mmc HS200)

 // MMC_CAP2_HS200_1_8V_SDR
 # echo $((1 << 5)) > /sys/module/mmc_core/parameters/caps2_clear

 # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/unbind
 # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/bind
 # grep timing /sys/kernel/debug/mmc0/ios
 timing spec:	8 (mmc DDR52)

 // MMC_CAP_1_8V_DDR
 # echo $((1 << 12)) > /sys/module/mmc_core/parameters/caps_clear

 # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/unbind
 # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/bind
 # grep timing /sys/kernel/debug/mmc0/ios
 timing spec:	1 (mmc high-speed)

 # echo 0 > /sys/module/mmc_core/parameters/caps2_clear

 # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/unbind
 # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/bind
 # grep timing /sys/kernel/debug/mmc0/ios
 timing spec:	9 (mmc HS200)

Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
---
 drivers/mmc/core/host.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

Comments

Ulf Hansson June 23, 2022, 1:53 p.m. UTC | #1
On Thu, 23 Jun 2022 at 10:00, Vincent Whitchurch
<vincent.whitchurch@axis.com> wrote:
>
> During board verification, there is a need to test the various supported
> eMMC/SD speed modes.  However, since the framework chooses the best mode
> supported by the card and the host controller's caps, this currently
> necessitates changing the devicetree for every iteration.
>
> To make changing the modes easier, allow the various host controller
> capabilities to be cleared via a module parameter.  (A per-controller
> debugfs wouldn't work since the controller needs to be re-probed to
> trigger re-init of cards.  A module parameter is used instead of a

I think we could make use of a per-controller debugfs thing, if used
in combination with MMC_CAP_AGGRESSIVE_PM and runtime PM.

As runtime PM also has sysfs interface for each device, we can control
runtime PM for the card's device (to trigger re-initialization of the
card). In between runtime suspend/resume of the card's device, we
should be able to change the supported speed modes, through debug fs.

Would this work for you?

Kind regards
Uffe

> global debugfs to allow this to be also set via the kernel command
> line.)
>
> The values to be written are the raw MMC_CAP* values from
> include/linux/mmc/host.h.  This is rather low-level, and these defines
> are not guaranteed to be stable, but it is perhaps good enough for the
> indented use case.  A warning is emitted when the caps clearing is in
> effect.
>
> Example of use:
>
>  # grep timing /sys/kernel/debug/mmc0/ios
>  timing spec:   9 (mmc HS200)
>
>  // MMC_CAP2_HS200_1_8V_SDR
>  # echo $((1 << 5)) > /sys/module/mmc_core/parameters/caps2_clear
>
>  # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/unbind
>  # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/bind
>  # grep timing /sys/kernel/debug/mmc0/ios
>  timing spec:   8 (mmc DDR52)
>
>  // MMC_CAP_1_8V_DDR
>  # echo $((1 << 12)) > /sys/module/mmc_core/parameters/caps_clear
>
>  # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/unbind
>  # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/bind
>  # grep timing /sys/kernel/debug/mmc0/ios
>  timing spec:   1 (mmc high-speed)
>
>  # echo 0 > /sys/module/mmc_core/parameters/caps2_clear
>
>  # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/unbind
>  # echo 16d40000.mmc > /sys/bus/platform/drivers/dwmmc_exynos/bind
>  # grep timing /sys/kernel/debug/mmc0/ios
>  timing spec:   9 (mmc HS200)
>
> Signed-off-by: Vincent Whitchurch <vincent.whitchurch@axis.com>
> ---
>  drivers/mmc/core/host.c | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
>
> diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
> index 2ed2b4d5e5a5..37971b7c7f62 100644
> --- a/drivers/mmc/core/host.c
> +++ b/drivers/mmc/core/host.c
> @@ -34,6 +34,10 @@
>  #define cls_dev_to_mmc_host(d) container_of(d, struct mmc_host, class_dev)
>
>  static DEFINE_IDA(mmc_host_ida);
> +static unsigned int caps_clear, caps2_clear;
> +
> +module_param(caps_clear, uint, 0644);
> +module_param(caps2_clear, uint, 0644);
>
>  #ifdef CONFIG_PM_SLEEP
>  static int mmc_host_class_prepare(struct device *dev)
> @@ -411,6 +415,14 @@ int mmc_of_parse(struct mmc_host *host)
>                 host->caps2 &= ~(MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V |
>                                  MMC_CAP2_HS400_ES);
>
> +       if (caps_clear || caps2_clear)
> +               dev_warn(host->parent,
> +                        "clearing host controller caps %#x caps2 %#x\n",
> +                        caps_clear, caps2_clear);
> +
> +       host->caps &= ~caps_clear;
> +       host->caps2 &= ~caps2_clear;
> +
>         /* Must be after "non-removable" check */
>         if (device_property_read_u32(dev, "fixed-emmc-driver-type", &drv_type) == 0) {
>                 if (host->caps & MMC_CAP_NONREMOVABLE)
> --
> 2.34.1
>
Vincent Whitchurch June 27, 2022, 10:08 a.m. UTC | #2
On Thu, Jun 23, 2022 at 03:53:41PM +0200, Ulf Hansson wrote:
> On Thu, 23 Jun 2022 at 10:00, Vincent Whitchurch
> <vincent.whitchurch@axis.com> wrote:
> > During board verification, there is a need to test the various supported
> > eMMC/SD speed modes.  However, since the framework chooses the best mode
> > supported by the card and the host controller's caps, this currently
> > necessitates changing the devicetree for every iteration.
> >
> > To make changing the modes easier, allow the various host controller
> > capabilities to be cleared via a module parameter.  (A per-controller
> > debugfs wouldn't work since the controller needs to be re-probed to
> > trigger re-init of cards.  A module parameter is used instead of a
> 
> I think we could make use of a per-controller debugfs thing, if used
> in combination with MMC_CAP_AGGRESSIVE_PM and runtime PM.
> 
> As runtime PM also has sysfs interface for each device, we can control
> runtime PM for the card's device (to trigger re-initialization of the
> card). In between runtime suspend/resume of the card's device, we
> should be able to change the supported speed modes, through debug fs.
> 
> Would this work for you?

I got it to work with the below commands and the following patch.  Note
that:

(1) MMC_CAP_AGGRESSIVE_PM also needs to be turned on via debugfs to
    avoid having to patch the kernel.  The cap is checked on every call
    to runtime_suspend so it (currently) works to set it without
    re-probing the host.

(2) I had to call mmc_select_card_type() even if there is an old card
    since currently it's only called from mmc_decode_ext_csd().

Also, unlike the module parameter, this can't be set from bootargs, but
that part is not important for my use case.

 root@(none):/sys/kernel/debug/mmc0# grep timing ios
 timing spec:	9 (mmc HS200)
 
 // Turn on MMC_CAP_AGGRESSIVE_PM and re-trigger runtime suspend
 root@(none):/sys/kernel/debug/mmc0# echo $(($(cat caps) | (1 << 7))) > caps
 root@(none):/sys/kernel/debug/mmc0# echo on > /sys/bus/mmc/devices/mmc0\:0001/power/control
 root@(none):/sys/kernel/debug/mmc0# echo auto > /sys/bus/mmc/devices/mmc0\:0001/power/control
 
 // MMC_CAP2_HS200_1_8V_SDR
 root@(none):/sys/kernel/debug/mmc0# echo $(($(cat caps2) & ~(1 << 5))) > caps2
 root@(none):/sys/kernel/debug/mmc0# echo on > /sys/bus/mmc/devices/mmc0\:0001/power/control
 root@(none):/sys/kernel/debug/mmc0# grep timing ios
 timing spec:	8 (mmc DDR52)

8<----
diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c
index 3fdbc801e64a..721925300611 100644
--- a/drivers/mmc/core/debugfs.c
+++ b/drivers/mmc/core/debugfs.c
@@ -12,9 +12,12 @@
 #include <linux/slab.h>
 #include <linux/stat.h>
 #include <linux/fault-inject.h>
+#include <linux/time.h>
 
 #include <linux/mmc/card.h>
 #include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
 
 #include "core.h"
 #include "card.h"
@@ -223,6 +226,47 @@ static int mmc_clock_opt_set(void *data, u64 val)
 DEFINE_DEBUGFS_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set,
 	"%llu\n");
 
+static int mmc_caps_get(void *data, u64 *val)
+{
+	*val = *(u32 *)data;
+	return 0;
+}
+
+static int mmc_caps_set(void *data, u64 val)
+{
+	u32 *caps = data;
+	u32 diff = *caps ^ val;
+	u32 allowed = MMC_CAP_AGGRESSIVE_PM |
+		MMC_CAP_SD_HIGHSPEED |
+		MMC_CAP_MMC_HIGHSPEED |
+		MMC_CAP_UHS |
+		MMC_CAP_DDR;
+
+	if (diff & ~allowed)
+		return -EINVAL;
+
+	*caps = val;
+
+	return 0;
+}
+
+static int mmc_caps2_set(void *data, u64 val)
+{
+	u32 *caps = data;
+	u32 diff = *caps ^ val;
+	u32 allowed = MMC_CAP2_HSX00_1_8V | MMC_CAP2_HSX00_1_2V;
+
+	if (diff & ~allowed)
+		return -EINVAL;
+
+	*caps = val;
+
+	return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(mmc_caps_fops, mmc_caps_get, mmc_caps_set, "0x%08llx\n");
+DEFINE_DEBUGFS_ATTRIBUTE(mmc_caps2_fops, mmc_caps_get, mmc_caps2_set, "0x%08llx\n");
+
 void mmc_add_host_debugfs(struct mmc_host *host)
 {
 	struct dentry *root;
@@ -231,8 +275,10 @@ void mmc_add_host_debugfs(struct mmc_host *host)
 	host->debugfs_root = root;
 
 	debugfs_create_file("ios", S_IRUSR, root, host, &mmc_ios_fops);
-	debugfs_create_x32("caps", S_IRUSR, root, &host->caps);
-	debugfs_create_x32("caps2", S_IRUSR, root, &host->caps2);
+	debugfs_create_file("caps", S_IRUSR | S_IWUSR, root, &host->caps,
+			&mmc_caps_fops);
+	debugfs_create_file("caps2", S_IRUSR | S_IWUSR, root, &host->caps2,
+			&mmc_caps2_fops);
 	debugfs_create_file_unsafe("clock", S_IRUSR | S_IWUSR, root, host,
 				   &mmc_clock_fops);
 
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 89cd48fcec79..c79c26add3da 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -1730,7 +1730,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
 
 		/* Erase size depends on CSD and Extended CSD */
 		mmc_set_erase_size(card);
-	}
+	} else
+		mmc_select_card_type(card);
 
 	/* Enable ERASE_GRP_DEF. This bit is lost after a reset or power off. */
 	if (card->ext_csd.rev >= 3) {
Ulf Hansson July 12, 2022, 10:20 a.m. UTC | #3
On Mon, 27 Jun 2022 at 12:08, Vincent Whitchurch
<vincent.whitchurch@axis.com> wrote:
>
> On Thu, Jun 23, 2022 at 03:53:41PM +0200, Ulf Hansson wrote:
> > On Thu, 23 Jun 2022 at 10:00, Vincent Whitchurch
> > <vincent.whitchurch@axis.com> wrote:
> > > During board verification, there is a need to test the various supported
> > > eMMC/SD speed modes.  However, since the framework chooses the best mode
> > > supported by the card and the host controller's caps, this currently
> > > necessitates changing the devicetree for every iteration.
> > >
> > > To make changing the modes easier, allow the various host controller
> > > capabilities to be cleared via a module parameter.  (A per-controller
> > > debugfs wouldn't work since the controller needs to be re-probed to
> > > trigger re-init of cards.  A module parameter is used instead of a
> >
> > I think we could make use of a per-controller debugfs thing, if used
> > in combination with MMC_CAP_AGGRESSIVE_PM and runtime PM.
> >
> > As runtime PM also has sysfs interface for each device, we can control
> > runtime PM for the card's device (to trigger re-initialization of the
> > card). In between runtime suspend/resume of the card's device, we
> > should be able to change the supported speed modes, through debug fs.
> >
> > Would this work for you?
>
> I got it to work with the below commands and the following patch.  Note
> that:
>
> (1) MMC_CAP_AGGRESSIVE_PM also needs to be turned on via debugfs to
>     avoid having to patch the kernel.  The cap is checked on every call
>     to runtime_suspend so it (currently) works to set it without
>     re-probing the host.
>
> (2) I had to call mmc_select_card_type() even if there is an old card
>     since currently it's only called from mmc_decode_ext_csd().
>
> Also, unlike the module parameter, this can't be set from bootargs, but
> that part is not important for my use case.
>
>  root@(none):/sys/kernel/debug/mmc0# grep timing ios
>  timing spec:   9 (mmc HS200)
>
>  // Turn on MMC_CAP_AGGRESSIVE_PM and re-trigger runtime suspend
>  root@(none):/sys/kernel/debug/mmc0# echo $(($(cat caps) | (1 << 7))) > caps
>  root@(none):/sys/kernel/debug/mmc0# echo on > /sys/bus/mmc/devices/mmc0\:0001/power/control
>  root@(none):/sys/kernel/debug/mmc0# echo auto > /sys/bus/mmc/devices/mmc0\:0001/power/control
>
>  // MMC_CAP2_HS200_1_8V_SDR
>  root@(none):/sys/kernel/debug/mmc0# echo $(($(cat caps2) & ~(1 << 5))) > caps2
>  root@(none):/sys/kernel/debug/mmc0# echo on > /sys/bus/mmc/devices/mmc0\:0001/power/control
>  root@(none):/sys/kernel/debug/mmc0# grep timing ios
>  timing spec:   8 (mmc DDR52)
>
> 8<----
> diff --git a/drivers/mmc/core/debugfs.c b/drivers/mmc/core/debugfs.c
> index 3fdbc801e64a..721925300611 100644
> --- a/drivers/mmc/core/debugfs.c
> +++ b/drivers/mmc/core/debugfs.c
> @@ -12,9 +12,12 @@
>  #include <linux/slab.h>
>  #include <linux/stat.h>
>  #include <linux/fault-inject.h>
> +#include <linux/time.h>
>
>  #include <linux/mmc/card.h>
>  #include <linux/mmc/host.h>
> +#include <linux/mmc/mmc.h>
> +#include <linux/mmc/sd.h>
>
>  #include "core.h"
>  #include "card.h"
> @@ -223,6 +226,47 @@ static int mmc_clock_opt_set(void *data, u64 val)
>  DEFINE_DEBUGFS_ATTRIBUTE(mmc_clock_fops, mmc_clock_opt_get, mmc_clock_opt_set,
>         "%llu\n");
>
> +static int mmc_caps_get(void *data, u64 *val)
> +{
> +       *val = *(u32 *)data;
> +       return 0;
> +}
> +
> +static int mmc_caps_set(void *data, u64 val)
> +{
> +       u32 *caps = data;
> +       u32 diff = *caps ^ val;
> +       u32 allowed = MMC_CAP_AGGRESSIVE_PM |
> +               MMC_CAP_SD_HIGHSPEED |
> +               MMC_CAP_MMC_HIGHSPEED |
> +               MMC_CAP_UHS |
> +               MMC_CAP_DDR;
> +
> +       if (diff & ~allowed)
> +               return -EINVAL;
> +
> +       *caps = val;
> +
> +       return 0;
> +}
> +
> +static int mmc_caps2_set(void *data, u64 val)
> +{
> +       u32 *caps = data;
> +       u32 diff = *caps ^ val;
> +       u32 allowed = MMC_CAP2_HSX00_1_8V | MMC_CAP2_HSX00_1_2V;
> +
> +       if (diff & ~allowed)
> +               return -EINVAL;
> +
> +       *caps = val;
> +
> +       return 0;
> +}
> +
> +DEFINE_DEBUGFS_ATTRIBUTE(mmc_caps_fops, mmc_caps_get, mmc_caps_set, "0x%08llx\n");
> +DEFINE_DEBUGFS_ATTRIBUTE(mmc_caps2_fops, mmc_caps_get, mmc_caps2_set, "0x%08llx\n");
> +
>  void mmc_add_host_debugfs(struct mmc_host *host)
>  {
>         struct dentry *root;
> @@ -231,8 +275,10 @@ void mmc_add_host_debugfs(struct mmc_host *host)
>         host->debugfs_root = root;
>
>         debugfs_create_file("ios", S_IRUSR, root, host, &mmc_ios_fops);
> -       debugfs_create_x32("caps", S_IRUSR, root, &host->caps);
> -       debugfs_create_x32("caps2", S_IRUSR, root, &host->caps2);
> +       debugfs_create_file("caps", S_IRUSR | S_IWUSR, root, &host->caps,
> +                       &mmc_caps_fops);
> +       debugfs_create_file("caps2", S_IRUSR | S_IWUSR, root, &host->caps2,
> +                       &mmc_caps2_fops);
>         debugfs_create_file_unsafe("clock", S_IRUSR | S_IWUSR, root, host,
>                                    &mmc_clock_fops);
>
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
> index 89cd48fcec79..c79c26add3da 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -1730,7 +1730,8 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
>
>                 /* Erase size depends on CSD and Extended CSD */
>                 mmc_set_erase_size(card);
> -       }
> +       } else
> +               mmc_select_card_type(card);
>
>         /* Enable ERASE_GRP_DEF. This bit is lost after a reset or power off. */
>         if (card->ext_csd.rev >= 3) {

Overall, the approach seems reasonable to me. Perhaps we can
reorganize the code so mmc_select_card_type() is always called from
mmc_init_card(), rather than from mmc_decode_ext_csd() too.

Kind regards
Uffe
diff mbox series

Patch

diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 2ed2b4d5e5a5..37971b7c7f62 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -34,6 +34,10 @@ 
 #define cls_dev_to_mmc_host(d)	container_of(d, struct mmc_host, class_dev)
 
 static DEFINE_IDA(mmc_host_ida);
+static unsigned int caps_clear, caps2_clear;
+
+module_param(caps_clear, uint, 0644);
+module_param(caps2_clear, uint, 0644);
 
 #ifdef CONFIG_PM_SLEEP
 static int mmc_host_class_prepare(struct device *dev)
@@ -411,6 +415,14 @@  int mmc_of_parse(struct mmc_host *host)
 		host->caps2 &= ~(MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V |
 				 MMC_CAP2_HS400_ES);
 
+	if (caps_clear || caps2_clear)
+		dev_warn(host->parent,
+			 "clearing host controller caps %#x caps2 %#x\n",
+			 caps_clear, caps2_clear);
+
+	host->caps &= ~caps_clear;
+	host->caps2 &= ~caps2_clear;
+
 	/* Must be after "non-removable" check */
 	if (device_property_read_u32(dev, "fixed-emmc-driver-type", &drv_type) == 0) {
 		if (host->caps & MMC_CAP_NONREMOVABLE)