diff mbox series

[v3] platform/x86: Add new vga-switcheroo gmux driver for ACPI-driven muxes

Message ID 20200729210557.9195-1-ddadap@nvidia.com (mailing list archive)
State Superseded, archived
Headers show
Series [v3] platform/x86: Add new vga-switcheroo gmux driver for ACPI-driven muxes | expand

Commit Message

Daniel Dadap July 29, 2020, 9:05 p.m. UTC
Some upcoming notebook designs utilize display muxes driven by a
pair of ACPI methods, MXDM to query and configure the operational
mode of the mux (integrated only, discrete only, hybrid non-muxed,
hybrid with dynamic mux switching), and MXDS to query and set the
mux state when running in dynamic switch mode.

Add a vga-switcheroo driver to support switching the mux on systems
with the ACPI MXDM/MXDS interface. The mux mode cannot be changed
dynamically (calling MXDM to change the mode won't have effect until
the next boot, and calling MXDM to read the mux mode returns the
active mode, not the mode that will be enabled on next boot), and
MXDS only works when the mux mode is set to dynamic switch, so this
driver will fail to load when MXDM reports any non-dynamic mode.

This driver currently only supports systems with Intel integrated
graphics and NVIDIA discrete graphics. It will need to be updated if
designs are developed using the same interfaces which utilize GPUs
from other vendors.

v2,v3: misc. fixes suggested by Barnabás Pőcze <pobrn@protonmail.com>

Signed-off-by: Daniel Dadap <ddadap@nvidia.com>
---
 MAINTAINERS                      |   6 +
 drivers/platform/x86/Kconfig     |   9 ++
 drivers/platform/x86/Makefile    |   2 +
 drivers/platform/x86/mxds-gmux.c | 267 +++++++++++++++++++++++++++++++
 4 files changed, 284 insertions(+)
 create mode 100644 drivers/platform/x86/mxds-gmux.c

Comments

Lukas Wunner Aug. 10, 2020, 8:37 a.m. UTC | #1
On Wed, Jul 29, 2020 at 04:05:57PM -0500, Daniel Dadap wrote:
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions 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.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, see <http://www.gnu.org/licenses>.

This boilerplate is unnecessary, the SPDX identifier is sufficient.

> +static int mxds_gmux_switchto(enum vga_switcheroo_client_id);
> +static enum vga_switcheroo_client_id mxds_gmux_get_client_id(struct pci_dev *);
> +
> +static const struct vga_switcheroo_handler handler = {
> +	.switchto = mxds_gmux_switchto,
> +	.get_client_id = mxds_gmux_get_client_id,
> +};

Move the handler struct further down to avoid the forward declarations.

> + * Call MXDS with bit 0 set to change the current state.
> + * When changing state, clear bit 4 for iGPU and set bit 4 for dGPU.
[...]
> +enum mux_state_command {
> +	MUX_STATE_GET = 0,
> +	MUX_STATE_SET_IGPU = 0x01,
> +	MUX_STATE_SET_DGPU = 0x11,
> +};

It looks like the code comment is wrong and bit 1 (instead of bit 4) is
used to select the GPU.

> +static acpi_integer acpi_helper(acpi_handle handle, enum acpi_method method,
> +				acpi_integer action)
> +{
> +	union acpi_object arg;
> +	struct acpi_object_list in = {.count = 1, .pointer = &arg};
> +	acpi_integer ret;
> +	acpi_status status;
> +
> +	arg.integer.type = ACPI_TYPE_INTEGER;
> +	arg.integer.value = action;

Hm, why not use an initializer for "arg", as you do for "in"?

> +static enum vga_switcheroo_client_id mxds_gmux_get_client_id(
> +	struct pci_dev *dev)
> +{
> +	if (dev) {
> +		if (ig_dev && dev->vendor == ig_dev->vendor)
> +			return VGA_SWITCHEROO_IGD;
> +		if (dg_dev && dev->vendor == dg_dev->vendor)
> +			return VGA_SWITCHEROO_DIS;
> +	}

That's a little odd.  Why not use "ig_dev == dev" and "dg_dev == dev"?

> +static acpi_status find_acpi_methods(
> +	acpi_handle object, u32 nesting_level, void *context,
> +	void **return_value)
> +{
> +	acpi_handle search;
> +
> +	/* If either MXDM or MXDS is missing, we can't use this object */
> +	if (acpi_get_handle(object, "MXDM", &search))
> +		return 0;

Since this function returns an acpi_status, all the return statements
should use AE_OK intead of 0.

Otherwise LGTM.

Please cc dri-devel when respinning since this concerns vga_switcheroo.

I'm also cc'ing Peter Wu who has lots of experience with hybrid graphics
through his involvement with Bumblebee, hence might be interested.
Searching through his collection of ACPI dumps, it seems MXDS and MXMX
have been present for years, but not MXDM:

https://github.com/search?q=user%3ALekensteyn+MXDS&type=Code

Thanks,

Lukas
Daniel Dadap Aug. 10, 2020, 6:44 p.m. UTC | #2
On 8/10/20 3:37 AM, Lukas Wunner wrote:
> External email: Use caution opening links or attachments
>
>
> On Wed, Jul 29, 2020 at 04:05:57PM -0500, Daniel Dadap wrote:
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions 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.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program; if not, see <http://www.gnu.org/licenses>.
> This boilerplate is unnecessary, the SPDX identifier is sufficient.


I don't doubt that it's unnecessary, but this is the recommended 
boilerplate license text for NVIDIA-copyrighted GPLv2-licensed code by 
NVIDIA legal. Unless there's a compelling reason to omit it, I'll leave 
it as-is. If anybody feels strongly about removing it, I can ask our 
legal team for guidance.


>
>> +static int mxds_gmux_switchto(enum vga_switcheroo_client_id);
>> +static enum vga_switcheroo_client_id mxds_gmux_get_client_id(struct pci_dev *);
>> +
>> +static const struct vga_switcheroo_handler handler = {
>> +     .switchto = mxds_gmux_switchto,
>> +     .get_client_id = mxds_gmux_get_client_id,
>> +};
> Move the handler struct further down to avoid the forward declarations.
>

Sure.


>> + * Call MXDS with bit 0 set to change the current state.
>> + * When changing state, clear bit 4 for iGPU and set bit 4 for dGPU.
> [...]
>> +enum mux_state_command {
>> +     MUX_STATE_GET = 0,
>> +     MUX_STATE_SET_IGPU = 0x01,
>> +     MUX_STATE_SET_DGPU = 0x11,
>> +};
> It looks like the code comment is wrong and bit 1 (instead of bit 4) is
> used to select the GPU.


The code comment is correct. The enum values are hexadecimal, not 
binary. Would it be clearer to write it out as something like 0 << 4 & 1 
<< 0 for MUX_STATE_SET_IGPU and 1 << 4 & 1 << 0 for MUX_STATE_SET_DGPU?


>> +static acpi_integer acpi_helper(acpi_handle handle, enum acpi_method method,
>> +                             acpi_integer action)
>> +{
>> +     union acpi_object arg;
>> +     struct acpi_object_list in = {.count = 1, .pointer = &arg};
>> +     acpi_integer ret;
>> +     acpi_status status;
>> +
>> +     arg.integer.type = ACPI_TYPE_INTEGER;
>> +     arg.integer.value = action;
> Hm, why not use an initializer for "arg", as you do for "in"?
>

Sure.


>> +static enum vga_switcheroo_client_id mxds_gmux_get_client_id(
>> +     struct pci_dev *dev)
>> +{
>> +     if (dev) {
>> +             if (ig_dev && dev->vendor == ig_dev->vendor)
>> +                     return VGA_SWITCHEROO_IGD;
>> +             if (dg_dev && dev->vendor == dg_dev->vendor)
>> +                     return VGA_SWITCHEROO_DIS;
>> +     }
> That's a little odd.  Why not use "ig_dev == dev" and "dg_dev == dev"?


No reason; that is indeed better. I think originally these comparisons 
got a vendor ID from some other means.


>
>> +static acpi_status find_acpi_methods(
>> +     acpi_handle object, u32 nesting_level, void *context,
>> +     void **return_value)
>> +{
>> +     acpi_handle search;
>> +
>> +     /* If either MXDM or MXDS is missing, we can't use this object */
>> +     if (acpi_get_handle(object, "MXDM", &search))
>> +             return 0;
> Since this function returns an acpi_status, all the return statements
> should use AE_OK intead of 0.


Okay.


> Otherwise LGTM.
>
> Please cc dri-devel when respinning since this concerns vga_switcheroo.


Will do, after testing the updated change. I'll leave the GPL 
boilerplate text and hexadecimal enum definitions as-is unless I hear 
otherwise.


> I'm also cc'ing Peter Wu who has lots of experience with hybrid graphics
> through his involvement with Bumblebee, hence might be interested.
> Searching through his collection of ACPI dumps, it seems MXDS and MXMX
> have been present for years, but not MXDM:
>
> https://github.com/search?q=user%3ALekensteyn+MXDS&type=Code


Yes, MXMX and MXDS go back a ways, it seems. I'm not familiar enough 
with the MXMX+MXDS designs to know if MXDS uses the same API in those 
designs as it doesn in the MXDM+MXDS designs. I'm not aware of any 
already available designs with MXDM. MXMX was used for switching DDC 
lines independently back when LVDS panels were the norm. The upcoming 
MXDM+MXDS designs are eDP-based and do not support independent DDC muxing.


> Thanks,
>
> Lukas
Lukas Wunner Aug. 11, 2020, 3:43 a.m. UTC | #3
On Mon, Aug 10, 2020 at 01:44:58PM -0500, Daniel Dadap wrote:
> On 8/10/20 3:37 AM, Lukas Wunner wrote:
> > On Wed, Jul 29, 2020 at 04:05:57PM -0500, Daniel Dadap wrote:
> > > + * Call MXDS with bit 0 set to change the current state.
> > > + * When changing state, clear bit 4 for iGPU and set bit 4 for dGPU.
> > [...]
> > > +enum mux_state_command {
> > > +     MUX_STATE_GET = 0,
> > > +     MUX_STATE_SET_IGPU = 0x01,
> > > +     MUX_STATE_SET_DGPU = 0x11,
> > > +};
> > It looks like the code comment is wrong and bit 1 (instead of bit 4) is
> > used to select the GPU.
> 
> The code comment is correct. The enum values are hexadecimal, not binary.

Ugh, missed that, sorry for the noise.

> Would it be clearer to write it out as something like 0 << 4 & 1 << 0 for
> MUX_STATE_SET_IGPU and 1 << 4 & 1 << 0 for MUX_STATE_SET_DGPU?

BIT(4) | BIT(0) might be clearer, but that gives you an unsigned long
and I'm not sure if gcc accepts that as an enum (=int) initializer.

> > > +static enum vga_switcheroo_client_id mxds_gmux_get_client_id(
> > > +     struct pci_dev *dev)
> > > +{
> > > +     if (dev) {
> > > +             if (ig_dev && dev->vendor == ig_dev->vendor)
> > > +                     return VGA_SWITCHEROO_IGD;
> > > +             if (dg_dev && dev->vendor == dg_dev->vendor)
> > > +                     return VGA_SWITCHEROO_DIS;
> > > +     }
> > That's a little odd.  Why not use "ig_dev == dev" and "dg_dev == dev"?
> 
> No reason; that is indeed better. I think originally these comparisons got a
> vendor ID from some other means.

Perhaps it was necessary in case an eGPU is attached, but that shouldn't
be an issue if you filter out Thunderbolt devices with
pci_is_thunderbolt_attached().

> Yes, MXMX and MXDS go back a ways, it seems. I'm not familiar enough with
> the MXMX+MXDS designs to know if MXDS uses the same API in those designs as
> it doesn in the MXDM+MXDS designs. I'm not aware of any already available
> designs with MXDM. MXMX was used for switching DDC lines independently back
> when LVDS panels were the norm. The upcoming MXDM+MXDS designs are eDP-based
> and do not support independent DDC muxing.

Interesting, thank you for the explanation.

Lukas
Daniel Dadap Aug. 11, 2020, 9:22 p.m. UTC | #4
On 8/10/20 10:43 PM, Lukas Wunner wrote:
> On Mon, Aug 10, 2020 at 01:44:58PM -0500, Daniel Dadap wrote:
>
>> Would it be clearer to write it out as something like 0 << 4 & 1 << 0 for
>> MUX_STATE_SET_IGPU and 1 << 4 & 1 << 0 for MUX_STATE_SET_DGPU?
> BIT(4) | BIT(0) might be clearer, but that gives you an unsigned long
> and I'm not sure if gcc accepts that as an enum (=int) initializer.


Ah yes, I forgot the BIT() macro. And it does seem to work just fine for 
initializing enum values, and there does seem to be precedent in other 
code in the kernel for doing so. And of course I meant | instead of & 
earlier. :)
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index eeff55560759..636c9259b345 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11510,6 +11510,12 @@  L:	linux-usb@vger.kernel.org
 S:	Maintained
 F:	drivers/usb/musb/
 
+MXDS GMUX DRIVER
+M:	Daniel Dadap <ddadap@nvidia.com>
+L:	platform-driver-x86@vger.kernel.org
+S:	Supported
+F:	drivers/platform/x86/mxds-gmux.c
+
 MXL301RF MEDIA DRIVER
 M:	Akihiro Tsukada <tskd08@gmail.com>
 L:	linux-media@vger.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 0ad7ad8cf8e1..5d00ad1ffc0e 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -1368,6 +1368,15 @@  config INTEL_TELEMETRY
 	  directly via debugfs files. Various tools may use
 	  this interface for SoC state monitoring.
 
+config MXDS_GMUX
+	tristate "ACPI MXDS Gmux Driver"
+	depends on ACPI_WMI
+	depends on ACPI
+	depends on VGA_SWITCHEROO
+	help
+	  This driver provides support for ACPI-driven gmux devices which are
+	  present on some notebook designs with hybrid graphics.
+
 endif # X86_PLATFORM_DEVICES
 
 config PMC_ATOM
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 53408d965874..b79000733fae 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -146,3 +146,5 @@  obj-$(CONFIG_INTEL_TELEMETRY)		+= intel_telemetry_core.o \
 					   intel_telemetry_pltdrv.o \
 					   intel_telemetry_debugfs.o
 obj-$(CONFIG_PMC_ATOM)			+= pmc_atom.o
+
+obj-$(CONFIG_MXDS_GMUX)			+= mxds-gmux.o
diff --git a/drivers/platform/x86/mxds-gmux.c b/drivers/platform/x86/mxds-gmux.c
new file mode 100644
index 000000000000..070a64fc1b25
--- /dev/null
+++ b/drivers/platform/x86/mxds-gmux.c
@@ -0,0 +1,267 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * mxds-gmux: vga_switcheroo mux handler for ACPI MXDS muxes
+ *
+ * Copyright (C) 2020 NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses>.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/acpi.h>
+#include <linux/pci.h>
+#include <linux/vga_switcheroo.h>
+#include <linux/delay.h>
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("vga_switcheroo mux handler for ACPI MXDS muxes");
+MODULE_AUTHOR("Daniel Dadap <ddadap@nvidia.com>");
+
+/*
+ * The mux doesn't have its own ACPI HID/CID, or WMI wrapper, so key off of
+ * the WMI wrapper for the related WMAA method for backlight control.
+ */
+MODULE_ALIAS("wmi:603E9613-EF25-4338-A3D0-C46177516DB7");
+
+static struct pci_dev *ig_dev, *dg_dev;
+static acpi_handle internal_mux_handle;
+static acpi_handle external_mux_handle;
+
+static int mxds_gmux_switchto(enum vga_switcheroo_client_id);
+static enum vga_switcheroo_client_id mxds_gmux_get_client_id(struct pci_dev *);
+
+static const struct vga_switcheroo_handler handler = {
+	.switchto = mxds_gmux_switchto,
+	.get_client_id = mxds_gmux_get_client_id,
+};
+
+enum acpi_method {
+	MXDM = 0,
+	MXDS,
+	NUM_ACPI_METHODS
+};
+
+static char *acpi_methods[NUM_ACPI_METHODS] = {
+	[MXDM] = "MXDM",
+	[MXDS] = "MXDS",
+};
+
+enum mux_mode_command {
+	MUX_MODE_GET = 0,
+};
+
+enum mux_mode {
+	MUX_MODE_DGPU_ONLY = 1,
+	MUX_MODE_IGPU_ONLY = 2,
+	MUX_MODE_MSHYBRID = 3,	/* Dual GPU, mux switched to iGPU */
+	MUX_MODE_DYNAMIC = 4,	/* Dual GPU, dynamic mux switching */
+};
+
+/*
+ * Call MXDS with argument value 0 to read the current state.
+ * When reading, a return value of 1 means iGPU and 2 means dGPU.
+ * Call MXDS with bit 0 set to change the current state.
+ * When changing state, clear bit 4 for iGPU and set bit 4 for dGPU.
+ */
+
+enum mux_state {
+	MUX_STATE_IGPU = 1,
+	MUX_STATE_DGPU = 2,
+};
+
+enum mux_state_command {
+	MUX_STATE_GET = 0,
+	MUX_STATE_SET_IGPU = 0x01,
+	MUX_STATE_SET_DGPU = 0x11,
+};
+
+static acpi_integer acpi_helper(acpi_handle handle, enum acpi_method method,
+				acpi_integer action)
+{
+	union acpi_object arg;
+	struct acpi_object_list in = {.count = 1, .pointer = &arg};
+	acpi_integer ret;
+	acpi_status status;
+
+	arg.integer.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = action;
+
+	status = acpi_evaluate_integer(handle, acpi_methods[method], &in, &ret);
+
+	if (ACPI_FAILURE(status)) {
+		pr_err("ACPI %s failed: %s\n", acpi_methods[method],
+			acpi_format_exception(status));
+		return 0;
+	}
+
+	return ret;
+}
+
+static acpi_integer get_mux_mode(acpi_handle handle)
+{
+	return acpi_helper(handle, MXDM, MUX_MODE_GET);
+}
+
+static acpi_integer get_mux_state(acpi_handle handle)
+{
+	return acpi_helper(handle, MXDS, MUX_STATE_GET);
+}
+
+static void set_mux_state(acpi_handle handle, enum mux_state state)
+{
+	enum mux_state_command command;
+
+	switch (state) {
+	case MUX_STATE_IGPU:
+		command = MUX_STATE_SET_IGPU;
+		break;
+	case MUX_STATE_DGPU:
+		command = MUX_STATE_SET_DGPU;
+		break;
+	default:
+		return;
+	}
+
+	acpi_helper(handle, MXDS, command);
+}
+
+static int mxds_gmux_switchto(enum vga_switcheroo_client_id id)
+{
+	enum mux_state state;
+
+	switch (id) {
+	case VGA_SWITCHEROO_IGD:
+		state = MUX_STATE_IGPU;
+		break;
+	case VGA_SWITCHEROO_DIS:
+		state = MUX_STATE_DGPU;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (internal_mux_handle) {
+		set_mux_state(internal_mux_handle, state);
+		if (get_mux_state(internal_mux_handle) != state)
+			return -EAGAIN;
+	}
+
+	if (external_mux_handle) {
+		set_mux_state(external_mux_handle, state);
+		if (get_mux_state(external_mux_handle) != state)
+			return -EAGAIN;
+	}
+
+	/* DP AUX can take up to 100ms to settle after mux switch */
+	mdelay(100);
+
+	return 0;
+}
+
+static enum vga_switcheroo_client_id mxds_gmux_get_client_id(
+	struct pci_dev *dev)
+{
+	if (dev) {
+		if (ig_dev && dev->vendor == ig_dev->vendor)
+			return VGA_SWITCHEROO_IGD;
+		if (dg_dev && dev->vendor == dg_dev->vendor)
+			return VGA_SWITCHEROO_DIS;
+	}
+
+	return VGA_SWITCHEROO_UNKNOWN_ID;
+}
+
+static acpi_status find_acpi_methods(
+	acpi_handle object, u32 nesting_level, void *context,
+	void **return_value)
+{
+	acpi_handle search;
+
+	/* If either MXDM or MXDS is missing, we can't use this object */
+	if (acpi_get_handle(object, "MXDM", &search))
+		return 0;
+	if (acpi_get_handle(object, "MXDS", &search))
+		return 0;
+
+	/* MXDS only works when MXDM indicates dynamic mode */
+	if (get_mux_mode(object) != MUX_MODE_DYNAMIC)
+		return 0;
+
+	/* Internal display has _BCL; external does not */
+	if (acpi_get_handle(object, "_BCL", &search))
+		external_mux_handle = object;
+	else
+		internal_mux_handle = object;
+
+	return 0;
+}
+
+static int __init mxds_gmux_init(void)
+{
+	int ret = 0;
+	struct pci_dev *dev = NULL;
+
+	/* Currently only supports Intel integrated and NVIDIA discrete GPUs */
+	while ((dev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, dev))) {
+		switch (dev->vendor) {
+		case PCI_VENDOR_ID_INTEL:
+			pci_dev_put(ig_dev);
+			ig_dev = pci_dev_get(dev);
+			break;
+		case PCI_VENDOR_ID_NVIDIA:
+			pci_dev_put(dg_dev);
+			dg_dev = pci_dev_get(dev);
+			break;
+		default:
+			break;
+		}
+	}
+
+	/* Require both integrated and discrete GPUs */
+	if (!ig_dev || !dg_dev) {
+		ret = -ENODEV;
+		goto done;
+	}
+
+	acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX,
+		find_acpi_methods, NULL, NULL, NULL);
+
+	/* Require at least one mux */
+	if (!internal_mux_handle && !external_mux_handle) {
+		ret = -ENODEV;
+		goto done;
+	}
+
+	ret = vga_switcheroo_register_handler(&handler, 0);
+
+done:
+
+	if (ret) {
+		pci_dev_put(ig_dev);
+		pci_dev_put(dg_dev);
+	}
+
+	return ret;
+}
+module_init(mxds_gmux_init);
+
+static void __exit mxds_gmux_exit(void)
+{
+	vga_switcheroo_unregister_handler();
+	pci_dev_put(ig_dev);
+	pci_dev_put(dg_dev);
+}
+module_exit(mxds_gmux_exit);