diff mbox series

[2/2] LoongArch: Add ACPI-based generic laptop driver

Message ID 20220815124803.3332991-2-chenhuacai@loongson.cn (mailing list archive)
State Superseded, archived
Headers show
Series [1/2] LoongArch: Add CPU HWMon platform driver | expand

Commit Message

Huacai Chen Aug. 15, 2022, 12:48 p.m. UTC
From: Jianmin Lv <lvjianmin@loongson.cn>

This add ACPI-based generic laptop driver for Loongson-3. Some of the
codes are derived from drivers/platform/x86/thinkpad_acpi.c.

Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
---
 drivers/platform/loongarch/Kconfig          |  18 +
 drivers/platform/loongarch/Makefile         |   1 +
 drivers/platform/loongarch/generic-laptop.c | 775 ++++++++++++++++++++
 3 files changed, 794 insertions(+)
 create mode 100644 drivers/platform/loongarch/generic-laptop.c

Comments

kernel test robot Aug. 15, 2022, 6:40 p.m. UTC | #1
Hi Huacai,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on soc/for-next]
[also build test WARNING on linus/master v6.0-rc1 next-20220815]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Huacai-Chen/LoongArch-Add-CPU-HWMon-platform-driver/20220815-205142
base:   https://git.kernel.org/pub/scm/linux/kernel/git/soc/soc.git for-next
config: loongarch-allyesconfig (https://download.01.org/0day-ci/archive/20220816/202208160225.IH6Wo0jU-lkp@intel.com/config)
compiler: loongarch64-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/a1d11c660bcd67e4483546b5b59b6cbe5c2a882f
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review Huacai-Chen/LoongArch-Add-CPU-HWMon-platform-driver/20220815-205142
        git checkout a1d11c660bcd67e4483546b5b59b6cbe5c2a882f
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=loongarch SHELL=/bin/bash drivers/platform/

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/platform/loongarch/generic-laptop.c:443:5: warning: no previous prototype for 'ec_get_brightness' [-Wmissing-prototypes]
     443 | int ec_get_brightness(void)
         |     ^~~~~~~~~~~~~~~~~
>> drivers/platform/loongarch/generic-laptop.c:460:5: warning: no previous prototype for 'ec_set_brightness' [-Wmissing-prototypes]
     460 | int ec_set_brightness(int level)
         |     ^~~~~~~~~~~~~~~~~
>> drivers/platform/loongarch/generic-laptop.c:475:5: warning: no previous prototype for 'ec_backlight_level' [-Wmissing-prototypes]
     475 | int ec_backlight_level(u8 level)
         |     ^~~~~~~~~~~~~~~~~~
>> drivers/platform/loongarch/generic-laptop.c:546:5: warning: no previous prototype for 'turn_on_backlight' [-Wmissing-prototypes]
     546 | int turn_on_backlight(void)
         |     ^~~~~~~~~~~~~~~~~
>> drivers/platform/loongarch/generic-laptop.c:562:5: warning: no previous prototype for 'turn_off_backlight' [-Wmissing-prototypes]
     562 | int turn_off_backlight(void)
         |     ^~~~~~~~~~~~~~~~~~


vim +/ec_get_brightness +443 drivers/platform/loongarch/generic-laptop.c

   442	
 > 443	int ec_get_brightness(void)
   444	{
   445		int status = 0;
   446	
   447		if (!hkey_handle)
   448			return -ENXIO;
   449	
   450		if (!acpi_evalf(hkey_handle, &status, "ECBG", "d"))
   451			return -EIO;
   452	
   453		if (status < 0)
   454			return status;
   455	
   456		return status;
   457	}
   458	EXPORT_SYMBOL(ec_get_brightness);
   459	
 > 460	int ec_set_brightness(int level)
   461	{
   462	
   463		int ret = 0;
   464	
   465		if (!hkey_handle)
   466			return -ENXIO;
   467	
   468		if (!acpi_evalf(hkey_handle, NULL, "ECBS", "vd", level))
   469			ret = -EIO;
   470	
   471		return ret;
   472	}
   473	EXPORT_SYMBOL(ec_set_brightness);
   474	
 > 475	int ec_backlight_level(u8 level)
   476	{
   477		int status = 0;
   478	
   479		if (!hkey_handle)
   480			return -ENXIO;
   481	
   482		if (!acpi_evalf(hkey_handle, &status, "ECLL", "d"))
   483			return -EIO;
   484	
   485		if ((status < 0) || (level > status))
   486			return status;
   487	
   488		if (!acpi_evalf(hkey_handle, &status, "ECSL", "d"))
   489			return -EIO;
   490	
   491		if ((status < 0) || (level < status))
   492			return status;
   493	
   494		return level;
   495	}
   496	EXPORT_SYMBOL(ec_backlight_level);
   497	
   498	static int loongson_laptop_backlight_update(struct backlight_device *bd)
   499	{
   500		int lvl = ec_backlight_level(bd->props.brightness);
   501	
   502		if (lvl < 0)
   503			return -EIO;
   504		if (ec_set_brightness(lvl))
   505			return -EIO;
   506	
   507		return 0;
   508	}
   509	
   510	static int loongson_laptop_get_brightness(struct backlight_device *bd)
   511	{
   512		u8 level;
   513	
   514		level = ec_get_brightness();
   515		if (level < 0)
   516			return -EIO;
   517	
   518		return level;
   519	}
   520	
   521	static const struct backlight_ops backlight_laptop_ops = {
   522		.update_status = loongson_laptop_backlight_update,
   523		.get_brightness = loongson_laptop_get_brightness,
   524	};
   525	
   526	static int laptop_backlight_register(void)
   527	{
   528		int status = 0;
   529		struct backlight_properties props;
   530	
   531		memset(&props, 0, sizeof(props));
   532		props.type = BACKLIGHT_PLATFORM;
   533	
   534		if (!acpi_evalf(hkey_handle, &status, "ECLL", "d"))
   535			return -EIO;
   536	
   537		props.brightness = 1;
   538		props.max_brightness = status;
   539	
   540		backlight_device_register("loongson_laptop",
   541					NULL, NULL, &backlight_laptop_ops, &props);
   542	
   543		return 0;
   544	}
   545	
 > 546	int turn_on_backlight(void)
   547	{
   548		int status;
   549		union acpi_object arg0 = { ACPI_TYPE_INTEGER };
   550		struct acpi_object_list args = { 1, &arg0 };
   551	
   552		arg0.integer.value = 1;
   553		status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
   554		if (ACPI_FAILURE(status)) {
   555			pr_info("Loongson lvds error: 0x%x\n", status);
   556			return -ENODEV;
   557		}
   558	
   559		return 0;
   560	}
   561	
 > 562	int turn_off_backlight(void)
   563	{
   564		int status;
   565		union acpi_object arg0 = { ACPI_TYPE_INTEGER };
   566		struct acpi_object_list args = { 1, &arg0 };
   567	
   568		arg0.integer.value = 0;
   569		status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
   570		if (ACPI_FAILURE(status)) {
   571			pr_info("Loongson lvds error: 0x%x\n", status);
   572			return -ENODEV;
   573		}
   574	
   575		return 0;
   576	}
   577
Arnd Bergmann Aug. 15, 2022, 7:11 p.m. UTC | #2
On Mon, Aug 15, 2022 at 2:48 PM Huacai Chen <chenhuacai@loongson.cn> wrote:
>
> From: Jianmin Lv <lvjianmin@loongson.cn>
>
> This add ACPI-based generic laptop driver for Loongson-3. Some of the
> codes are derived from drivers/platform/x86/thinkpad_acpi.c.
>
> Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
> Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
> ---
>  drivers/platform/loongarch/Kconfig          |  18 +
>  drivers/platform/loongarch/Makefile         |   1 +
>  drivers/platform/loongarch/generic-laptop.c | 775 ++++++++++++++++++++
>  3 files changed, 794 insertions(+)
>  create mode 100644 drivers/platform/loongarch/generic-laptop.c
>
> diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig
> index a1542843b0ad..086212d57251 100644
> --- a/drivers/platform/loongarch/Kconfig
> +++ b/drivers/platform/loongarch/Kconfig
> @@ -23,4 +23,22 @@ config CPU_HWMON
>         help
>           Loongson-3A/3B/3C CPU HWMon (temperature sensor) driver.
>
> +config GENERIC_LAPTOP
> +       tristate "Generic Loongson-3A Laptop Driver"
> +       depends on MACH_LOONGSON64
> +       depends on ACPI
> +       depends on INPUT
> +       select BACKLIGHT_CLASS_DEVICE
> +       select BACKLIGHT_LCD_SUPPORT
> +       select HWMON
> +       select INPUT_EVDEV
> +       select INPUT_SPARSEKMAP
> +       select LCD_CLASS_DEVICE
> +       select LEDS_CLASS
> +       select POWER_SUPPLY
> +       select VIDEO_OUTPUT_CONTROL
> +       default y
> +       help
> +         ACPI-based Loongson-3 family laptops generic driver.

It's rather bad style to 'select' entire subsystems from a device
driver. This may be
unavoidable in some cases, but please try to make it possible to build the
driver when some or all of the other subsystems are disabled. In a lot
of subsystems,
there is an API stub like

> +/****************************************************************************
> + ****************************************************************************
> + *
> + * ACPI Helpers and device model
> + *
> + ****************************************************************************
> + ****************************************************************************/

Try to follow the normal commenting style

> +/* ACPI basic handles */
> +
> +static int acpi_evalf(acpi_handle handle,
> +                     int *res, char *method, char *fmt, ...);
> +static acpi_handle ec_handle;

Instead of forward function declarations, try sorting the functions in
call order,
which has the benefit of making more sense to most readers.

> +#ifdef CONFIG_PM
> +static int loongson_generic_suspend(struct device *dev)
> +{
> +       return 0;
> +}
> +static int loongson_generic_resume(struct device *dev)
> +{
> +       int status = 0;
> +       struct key_entry ke;
> +       struct backlight_device *bd;
> +
> +       /*
> +        * Only if the firmware supports SW_LID event model, we can handle the
> +        * event. This is for the consideration of development board without
> +        * EC.
> +        */
> +       if (test_bit(SW_LID, generic_inputdev->swbit)) {
> +               if (hotkey_status_get(&status))
> +                       return -EIO;
> +               /*
> +                * The input device sw element records the last lid status.
> +                * When the system is awakened by other wake-up sources,
> +                * the lid event will also be reported. The judgment of
> +                * adding SW_LID bit which in sw element can avoid this
> +                * case.
> +                *
> +                * input system will drop lid event when current lid event
> +                * value and last lid status in the same data set,which
> +                * data set inclue zero set and no zero set. so laptop
> +                * driver doesn't report repeated events.
> +                *
> +                * Lid status is generally 0, but hardware exception is
> +                * considered. So add lid status confirmation.
> +                */
> +               if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
> +                       ke.type = KE_SW;
> +                       ke.sw.value = (u8)status;
> +                       ke.sw.code = SW_LID;
> +                       sparse_keymap_report_entry(generic_inputdev, &ke,
> +                                       1, true);
> +               }
> +       }
> +
> +       bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
> +       if (bd) {
> +               loongson_laptop_backlight_update(bd) ?
> +               pr_warn("Loongson_backlight:resume brightness failed") :
> +               pr_info("Loongson_backlight:resume brightness %d\n", bd->props.brightness);
> +       }
> +
> +       return 0;
> +}
> +
> +static SIMPLE_DEV_PM_OPS(loongson_generic_pm,
> +               loongson_generic_suspend, loongson_generic_resume);
> +#endif


Instead of the #ifdef, use the newer DEFINE_SIMPLE_DEV_PM_OPS() in place
of SIMPLE_DEV_PM_OPS() so the code will always be parsed but left out
when CONFIG_PM is disabled.

> +
> +static int __init register_generic_subdriver(struct generic_struct *sub_driver)
> +{
> +       int rc;
> +
> +       BUG_ON(!sub_driver->acpi);
> +
> +       sub_driver->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
> +       if (!sub_driver->acpi->driver) {
> +               pr_err("failed to allocate memory for ibm->acpi->driver\n");
> +               return -ENOMEM;
> +       }

Drivers should be statically allocated. Usually you want one 'struct
acpi_driver' or
'struct platform_driver' per file, so you can just use 'module_acpi_driver()'.

> +int ec_get_brightness(void)
> +{
> +       int status = 0;
> +
> +       if (!hkey_handle)
> +               return -ENXIO;
> +
> +       if (!acpi_evalf(hkey_handle, &status, "ECBG", "d"))
> +               return -EIO;
> +
> +       if (status < 0)
> +               return status;
> +
> +       return status;
> +}
> +EXPORT_SYMBOL(ec_get_brightness);

The name is too generic to have it in the global namespace for a platform
specific driver. Use a prefix to make it clear which driver this belongs to.

Not sure this function warrants an export though, it looks like you could
just have it in the caller module.

> +
> +int turn_off_backlight(void)
> +{
> +       int status;
> +       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> +       struct acpi_object_list args = { 1, &arg0 };
> +
> +       arg0.integer.value = 0;
> +       status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
> +       if (ACPI_FAILURE(status)) {
> +               pr_info("Loongson lvds error: 0x%x\n", status);
> +               return -ENODEV;
> +       }
> +
> +       return 0;
> +}

Again, the name is too generic for a global function.

I suspect that if you split out the backlight handling into a separate
driver, you can avoid
the 'select' statements completely and make that driver 'depends on
BACKLIGHT_CLASS_DEVICE'
or move it within the 'if BACKLIGHT_CLASS_DEVICE' section of
drivers/video/backlight/Kconfig.


> +static struct generic_acpi_drv_struct ec_event_acpidriver = {
> +       .hid = loongson_htk_device_ids,
> +       .notify = event_notify,
> +       .handle = &hkey_handle,
> +       .type = ACPI_DEVICE_NOTIFY,
> +};

Same here, this can probably just be an input driver in drivers/input.

       Arnd
Huacai Chen Aug. 16, 2022, 8:55 a.m. UTC | #3
Hi, Arnd,

On Tue, Aug 16, 2022 at 3:11 AM Arnd Bergmann <arnd@arndb.de> wrote:
>
> On Mon, Aug 15, 2022 at 2:48 PM Huacai Chen <chenhuacai@loongson.cn> wrote:
> >
> > From: Jianmin Lv <lvjianmin@loongson.cn>
> >
> > This add ACPI-based generic laptop driver for Loongson-3. Some of the
> > codes are derived from drivers/platform/x86/thinkpad_acpi.c.
> >
> > Signed-off-by: Jianmin Lv <lvjianmin@loongson.cn>
> > Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
> > ---
> >  drivers/platform/loongarch/Kconfig          |  18 +
> >  drivers/platform/loongarch/Makefile         |   1 +
> >  drivers/platform/loongarch/generic-laptop.c | 775 ++++++++++++++++++++
> >  3 files changed, 794 insertions(+)
> >  create mode 100644 drivers/platform/loongarch/generic-laptop.c
> >
> > diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig
> > index a1542843b0ad..086212d57251 100644
> > --- a/drivers/platform/loongarch/Kconfig
> > +++ b/drivers/platform/loongarch/Kconfig
> > @@ -23,4 +23,22 @@ config CPU_HWMON
> >         help
> >           Loongson-3A/3B/3C CPU HWMon (temperature sensor) driver.
> >
> > +config GENERIC_LAPTOP
> > +       tristate "Generic Loongson-3A Laptop Driver"
> > +       depends on MACH_LOONGSON64
> > +       depends on ACPI
> > +       depends on INPUT
> > +       select BACKLIGHT_CLASS_DEVICE
> > +       select BACKLIGHT_LCD_SUPPORT
> > +       select HWMON
> > +       select INPUT_EVDEV
> > +       select INPUT_SPARSEKMAP
> > +       select LCD_CLASS_DEVICE
> > +       select LEDS_CLASS
> > +       select POWER_SUPPLY
> > +       select VIDEO_OUTPUT_CONTROL
> > +       default y
> > +       help
> > +         ACPI-based Loongson-3 family laptops generic driver.
>
> It's rather bad style to 'select' entire subsystems from a device
> driver. This may be
> unavoidable in some cases, but please try to make it possible to build the
> driver when some or all of the other subsystems are disabled. In a lot
> of subsystems,
> there is an API stub like
OK, the Kconfig should be cleaned up, I will remove those unneeded
lines, and convert some others to "depends on".

>
> > +/****************************************************************************
> > + ****************************************************************************
> > + *
> > + * ACPI Helpers and device model
> > + *
> > + ****************************************************************************
> > + ****************************************************************************/
>
> Try to follow the normal commenting style
OK, thanks.

>
> > +/* ACPI basic handles */
> > +
> > +static int acpi_evalf(acpi_handle handle,
> > +                     int *res, char *method, char *fmt, ...);
> > +static acpi_handle ec_handle;
>
> Instead of forward function declarations, try sorting the functions in
> call order,
> which has the benefit of making more sense to most readers.
OK, thanks.

>
> > +#ifdef CONFIG_PM
> > +static int loongson_generic_suspend(struct device *dev)
> > +{
> > +       return 0;
> > +}
> > +static int loongson_generic_resume(struct device *dev)
> > +{
> > +       int status = 0;
> > +       struct key_entry ke;
> > +       struct backlight_device *bd;
> > +
> > +       /*
> > +        * Only if the firmware supports SW_LID event model, we can handle the
> > +        * event. This is for the consideration of development board without
> > +        * EC.
> > +        */
> > +       if (test_bit(SW_LID, generic_inputdev->swbit)) {
> > +               if (hotkey_status_get(&status))
> > +                       return -EIO;
> > +               /*
> > +                * The input device sw element records the last lid status.
> > +                * When the system is awakened by other wake-up sources,
> > +                * the lid event will also be reported. The judgment of
> > +                * adding SW_LID bit which in sw element can avoid this
> > +                * case.
> > +                *
> > +                * input system will drop lid event when current lid event
> > +                * value and last lid status in the same data set,which
> > +                * data set inclue zero set and no zero set. so laptop
> > +                * driver doesn't report repeated events.
> > +                *
> > +                * Lid status is generally 0, but hardware exception is
> > +                * considered. So add lid status confirmation.
> > +                */
> > +               if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
> > +                       ke.type = KE_SW;
> > +                       ke.sw.value = (u8)status;
> > +                       ke.sw.code = SW_LID;
> > +                       sparse_keymap_report_entry(generic_inputdev, &ke,
> > +                                       1, true);
> > +               }
> > +       }
> > +
> > +       bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
> > +       if (bd) {
> > +               loongson_laptop_backlight_update(bd) ?
> > +               pr_warn("Loongson_backlight:resume brightness failed") :
> > +               pr_info("Loongson_backlight:resume brightness %d\n", bd->props.brightness);
> > +       }
> > +
> > +       return 0;
> > +}
> > +
> > +static SIMPLE_DEV_PM_OPS(loongson_generic_pm,
> > +               loongson_generic_suspend, loongson_generic_resume);
> > +#endif
>
>
> Instead of the #ifdef, use the newer DEFINE_SIMPLE_DEV_PM_OPS() in place
> of SIMPLE_DEV_PM_OPS() so the code will always be parsed but left out
> when CONFIG_PM is disabled.
OK, thanks.

>
> > +
> > +static int __init register_generic_subdriver(struct generic_struct *sub_driver)
> > +{
> > +       int rc;
> > +
> > +       BUG_ON(!sub_driver->acpi);
> > +
> > +       sub_driver->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
> > +       if (!sub_driver->acpi->driver) {
> > +               pr_err("failed to allocate memory for ibm->acpi->driver\n");
> > +               return -ENOMEM;
> > +       }
>
> Drivers should be statically allocated. Usually you want one 'struct
> acpi_driver' or
> 'struct platform_driver' per file, so you can just use 'module_acpi_driver()'.
I found that "subdriver" in other laptop drivers also uses dynamical
allocation, because there may be various numbers of subdrivers. I want
to keep it, at least in the next version for review.

>
> > +int ec_get_brightness(void)
> > +{
> > +       int status = 0;
> > +
> > +       if (!hkey_handle)
> > +               return -ENXIO;
> > +
> > +       if (!acpi_evalf(hkey_handle, &status, "ECBG", "d"))
> > +               return -EIO;
> > +
> > +       if (status < 0)
> > +               return status;
> > +
> > +       return status;
> > +}
> > +EXPORT_SYMBOL(ec_get_brightness);
>
> The name is too generic to have it in the global namespace for a platform
> specific driver. Use a prefix to make it clear which driver this belongs to.
>
> Not sure this function warrants an export though, it looks like you could
> just have it in the caller module.
Yes, they should be static.

>
> > +
> > +int turn_off_backlight(void)
> > +{
> > +       int status;
> > +       union acpi_object arg0 = { ACPI_TYPE_INTEGER };
> > +       struct acpi_object_list args = { 1, &arg0 };
> > +
> > +       arg0.integer.value = 0;
> > +       status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
> > +       if (ACPI_FAILURE(status)) {
> > +               pr_info("Loongson lvds error: 0x%x\n", status);
> > +               return -ENODEV;
> > +       }
> > +
> > +       return 0;
> > +}
>
> Again, the name is too generic for a global function.
Yes, they should be renamed.

>
> I suspect that if you split out the backlight handling into a separate
> driver, you can avoid
> the 'select' statements completely and make that driver 'depends on
> BACKLIGHT_CLASS_DEVICE'
> or move it within the 'if BACKLIGHT_CLASS_DEVICE' section of
> drivers/video/backlight/Kconfig.
>
>
> > +static struct generic_acpi_drv_struct ec_event_acpidriver = {
> > +       .hid = loongson_htk_device_ids,
> > +       .notify = event_notify,
> > +       .handle = &hkey_handle,
> > +       .type = ACPI_DEVICE_NOTIFY,
> > +};
>
> Same here, this can probably just be an input driver in drivers/input.
It seems the existing "laptop drivers" are also complex drivers to
bind several "subdrivers" together.

Huacai
>
>        Arnd
>
Arnd Bergmann Aug. 16, 2022, 9:05 a.m. UTC | #4
On Tue, Aug 16, 2022 at 10:55 AM Huacai Chen <chenhuacai@kernel.org> wrote:
> On Tue, Aug 16, 2022 at 3:11 AM Arnd Bergmann <arnd@arndb.de> wrote:
> > On Mon, Aug 15, 2022 at 2:48 PM Huacai Chen <chenhuacai@loongson.cn> wrote:
> > > +
> > > +static int __init register_generic_subdriver(struct generic_struct *sub_driver)
> > > +{
> > > +       int rc;
> > > +
> > > +       BUG_ON(!sub_driver->acpi);
> > > +
> > > +       sub_driver->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
> > > +       if (!sub_driver->acpi->driver) {
> > > +               pr_err("failed to allocate memory for ibm->acpi->driver\n");
> > > +               return -ENOMEM;
> > > +       }
> >
> > Drivers should be statically allocated. Usually you want one 'struct
> > acpi_driver' or
> > 'struct platform_driver' per file, so you can just use 'module_acpi_driver()'.
> I found that "subdriver" in other laptop drivers also uses dynamical
> allocation, because there may be various numbers of subdrivers. I want
> to keep it, at least in the next version for review.

Fair enough, I'm not that familiar with drivers/platform/ conventions,
so this is
probably fine.  Adding the drivers/platform/x86 maintainers for clarification,
since your driver probably fits best into that general class regardless of the
CPU instruction set.

> > > +static struct generic_acpi_drv_struct ec_event_acpidriver = {
> > > +       .hid = loongson_htk_device_ids,
> > > +       .notify = event_notify,
> > > +       .handle = &hkey_handle,
> > > +       .type = ACPI_DEVICE_NOTIFY,
> > > +};
> >
> > Same here, this can probably just be an input driver in drivers/input.
>
> It seems the existing "laptop drivers" are also complex drivers to
> bind several "subdrivers" together.

Let's see what Hans and Mark think here as well. My feeling is that this
might be a little different. In the other laptop drivers you'd load a
module for a Dell/HP/Lenovo/Asus/Acer/... model and that has all the
bits that this vendor uses across the system, so a single driver makes
sense.

In embedded systems, you'd have SoC specific drivers that we tend
to put into the respective subsystems (backlight, led, input, ...) and
then users pick the drivers they want.

Loongarch is probably somewhere inbetween, since this is a chip
vendor rather than a laptop vendor, but you have all the same bits
here. The question is more about where we put it.

       Arnd
diff mbox series

Patch

diff --git a/drivers/platform/loongarch/Kconfig b/drivers/platform/loongarch/Kconfig
index a1542843b0ad..086212d57251 100644
--- a/drivers/platform/loongarch/Kconfig
+++ b/drivers/platform/loongarch/Kconfig
@@ -23,4 +23,22 @@  config CPU_HWMON
 	help
 	  Loongson-3A/3B/3C CPU HWMon (temperature sensor) driver.
 
+config GENERIC_LAPTOP
+	tristate "Generic Loongson-3A Laptop Driver"
+	depends on MACH_LOONGSON64
+	depends on ACPI
+	depends on INPUT
+	select BACKLIGHT_CLASS_DEVICE
+	select BACKLIGHT_LCD_SUPPORT
+	select HWMON
+	select INPUT_EVDEV
+	select INPUT_SPARSEKMAP
+	select LCD_CLASS_DEVICE
+	select LEDS_CLASS
+	select POWER_SUPPLY
+	select VIDEO_OUTPUT_CONTROL
+	default y
+	help
+	  ACPI-based Loongson-3 family laptops generic driver.
+
 endif # LOONGARCH_PLATFORM_DEVICES
diff --git a/drivers/platform/loongarch/Makefile b/drivers/platform/loongarch/Makefile
index 8dfd03924c37..9d6f69f2319d 100644
--- a/drivers/platform/loongarch/Makefile
+++ b/drivers/platform/loongarch/Makefile
@@ -1 +1,2 @@ 
 obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o
+obj-$(CONFIG_GENERIC_LAPTOP) += generic-laptop.o
diff --git a/drivers/platform/loongarch/generic-laptop.c b/drivers/platform/loongarch/generic-laptop.c
new file mode 100644
index 000000000000..90e37a02511d
--- /dev/null
+++ b/drivers/platform/loongarch/generic-laptop.c
@@ -0,0 +1,775 @@ 
+/*
+ *  Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
+ *
+ *  Jianmin Lv <lvjianmin@loongson.cn>
+ *  Huacai Chen <chenhuacai@loongson.cn>
+ *
+ * Copyright (C) 2022 Loongson Technology Corporation Limited
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/acpi.h>
+#include <linux/uaccess.h>
+#include <linux/input/sparse-keymap.h>
+#include <linux/device.h>
+#include <linux/backlight.h>
+#include <acpi/video.h>
+
+/* ACPI HIDs */
+#define LOONGSON_ACPI_HKEY_HID	"LOON0000"
+#define LOONGSON_ACPI_EC_HID	"PNP0C09"
+
+/****************************************************************************
+ * Main driver
+ */
+
+#define ACPI_LAPTOP_VERSION "1.0"
+#define ACPI_LAPTOP_NAME "loongson-laptop"
+#define ACPI_LAPTOP_DESC "Loongson Laptop/all-in-one ACPI Driver"
+#define ACPI_LAPTOP_FILE ACPI_LAPTOP_NAME "_acpi"
+#define ACPI_LAPTOP_DRVR_NAME ACPI_LAPTOP_FILE
+#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
+/****************************************************************************
+ * Driver-wide structs and misc. variables
+ */
+
+struct generic_struct;
+
+struct generic_acpi_drv_struct {
+	u32 type;
+	acpi_handle *handle;
+	const struct acpi_device_id *hid;
+	struct acpi_device *device;
+	struct acpi_driver *driver;
+	void (*notify)(struct generic_struct *, u32);
+};
+
+struct generic_struct {
+	char *name;
+
+	int (*init)(struct generic_struct *);
+
+	struct generic_acpi_drv_struct *acpi;
+
+	struct {
+		u8 acpi_driver_registered;
+		u8 acpi_notify_installed;
+	} flags;
+};
+
+
+static struct {
+	u32 input_device_registered:1;
+} generic_features;
+
+static int hotkey_status_get(int *status);
+static int loongson_laptop_backlight_update(struct backlight_device *bd);
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * ACPI Helpers and device model
+ *
+ ****************************************************************************
+ ****************************************************************************/
+
+/* ACPI basic handles */
+
+static int acpi_evalf(acpi_handle handle,
+		      int *res, char *method, char *fmt, ...);
+static acpi_handle ec_handle;
+
+#define GENERIC_HANDLE(object, parent, paths...)			\
+	static acpi_handle  object##_handle;			\
+	static const acpi_handle * const object##_parent __initconst =	\
+						&parent##_handle; \
+	static char *object##_paths[] __initdata = { paths }
+
+GENERIC_HANDLE(hkey, ec, "\\_SB.HKEY", "^HKEY", "HKEY",);
+
+/* ACPI device model */
+
+#define GENERIC_ACPIHANDLE_INIT(object) \
+	drv_acpi_handle_init(#object, &object##_handle, *object##_parent, \
+		object##_paths, ARRAY_SIZE(object##_paths))
+
+static void __init drv_acpi_handle_init(const char *name,
+			   acpi_handle *handle, const acpi_handle parent,
+			   char **paths, const int num_paths)
+{
+	int i;
+	acpi_status status;
+
+	for (i = 0; i < num_paths; i++) {
+		status = acpi_get_handle(parent, paths[i], handle);
+		if (ACPI_SUCCESS(status))
+			return;
+	}
+
+	*handle = NULL;
+}
+static acpi_status __init generic_acpi_handle_locate_callback(acpi_handle handle,
+					u32 level, void *context, void **return_value)
+{
+	*(acpi_handle *)return_value = handle;
+
+	return AE_CTRL_TERMINATE;
+}
+
+static void __init generic_acpi_handle_locate(const char *name,
+		const char *hid, acpi_handle *handle)
+{
+	acpi_status status;
+	acpi_handle device_found;
+
+	BUG_ON(!name || !hid || !handle);
+
+	*handle = NULL;
+
+	memset(&device_found, 0, sizeof(device_found));
+	status = acpi_get_devices(hid, generic_acpi_handle_locate_callback,
+				  (void *)name, &device_found);
+
+	if (ACPI_SUCCESS(status))
+		*handle = device_found;
+}
+
+static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct generic_struct *sub_driver = data;
+
+	if (!sub_driver || !sub_driver->acpi || !sub_driver->acpi->notify)
+		return;
+	sub_driver->acpi->notify(sub_driver, event);
+}
+
+static int __init setup_acpi_notify(struct generic_struct *sub_driver)
+{
+	acpi_status status;
+
+	BUG_ON(!sub_driver->acpi);
+
+	if (!*sub_driver->acpi->handle)
+		return 0;
+
+	sub_driver->acpi->device = acpi_fetch_acpi_dev(*sub_driver->acpi->handle);
+	if (!sub_driver->acpi->device) {
+		pr_err("acpi_fetch_acpi_dev(%s) failed\n", sub_driver->name);
+		return -ENODEV;
+	}
+
+	sub_driver->acpi->device->driver_data = sub_driver;
+	sprintf(acpi_device_class(sub_driver->acpi->device), "%s/%s",
+		ACPI_LAPTOP_ACPI_EVENT_PREFIX,
+		sub_driver->name);
+
+	status = acpi_install_notify_handler(*sub_driver->acpi->handle,
+			sub_driver->acpi->type, dispatch_acpi_notify, sub_driver);
+	if (ACPI_FAILURE(status)) {
+		if (status == AE_ALREADY_EXISTS) {
+			pr_notice("another device driver is already "
+				  "handling %s events\n", sub_driver->name);
+		} else {
+			pr_err("acpi_install_notify_handler(%s) failed: %s\n",
+			       sub_driver->name, acpi_format_exception(status));
+		}
+		return -ENODEV;
+	}
+	sub_driver->flags.acpi_notify_installed = 1;
+	return 0;
+}
+
+static int __init tpacpi_device_add(struct acpi_device *device)
+{
+	return 0;
+}
+
+static struct input_dev *generic_inputdev;
+
+#ifdef CONFIG_PM
+static int loongson_generic_suspend(struct device *dev)
+{
+	return 0;
+}
+static int loongson_generic_resume(struct device *dev)
+{
+	int status = 0;
+	struct key_entry ke;
+	struct backlight_device *bd;
+
+	/*
+	 * Only if the firmware supports SW_LID event model, we can handle the
+	 * event. This is for the consideration of development board without
+	 * EC.
+	 */
+	if (test_bit(SW_LID, generic_inputdev->swbit)) {
+		if (hotkey_status_get(&status))
+			return -EIO;
+		/*
+		 * The input device sw element records the last lid status.
+		 * When the system is awakened by other wake-up sources,
+		 * the lid event will also be reported. The judgment of
+		 * adding SW_LID bit which in sw element can avoid this
+		 * case.
+		 *
+		 * input system will drop lid event when current lid event
+		 * value and last lid status in the same data set,which
+		 * data set inclue zero set and no zero set. so laptop
+		 * driver doesn't report repeated events.
+		 *
+		 * Lid status is generally 0, but hardware exception is
+		 * considered. So add lid status confirmation.
+		 */
+		if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
+			ke.type = KE_SW;
+			ke.sw.value = (u8)status;
+			ke.sw.code = SW_LID;
+			sparse_keymap_report_entry(generic_inputdev, &ke,
+					1, true);
+		}
+	}
+
+	bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
+	if (bd) {
+		loongson_laptop_backlight_update(bd) ?
+		pr_warn("Loongson_backlight:resume brightness failed") :
+		pr_info("Loongson_backlight:resume brightness %d\n", bd->props.brightness);
+	}
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(loongson_generic_pm,
+		loongson_generic_suspend, loongson_generic_resume);
+#endif
+
+static int __init register_generic_subdriver(struct generic_struct *sub_driver)
+{
+	int rc;
+
+	BUG_ON(!sub_driver->acpi);
+
+	sub_driver->acpi->driver = kzalloc(sizeof(struct acpi_driver), GFP_KERNEL);
+	if (!sub_driver->acpi->driver) {
+		pr_err("failed to allocate memory for ibm->acpi->driver\n");
+		return -ENOMEM;
+	}
+
+	sprintf(sub_driver->acpi->driver->name, "%s_%s", ACPI_LAPTOP_NAME, sub_driver->name);
+	sub_driver->acpi->driver->ids = sub_driver->acpi->hid;
+	sub_driver->acpi->driver->ops.add = &tpacpi_device_add;
+#ifdef CONFIG_PM
+	sub_driver->acpi->driver->drv.pm = &loongson_generic_pm;
+#endif
+	rc = acpi_bus_register_driver(sub_driver->acpi->driver);
+	if (rc < 0) {
+		pr_err("acpi_bus_register_driver(%s) failed: %d\n",
+		       sub_driver->name, rc);
+		kfree(sub_driver->acpi->driver);
+		sub_driver->acpi->driver = NULL;
+	} else if (!rc)
+		sub_driver->flags.acpi_driver_registered = 1;
+
+	return rc;
+}
+
+/* Loongson generic laptop firmware event model */
+
+#define GENERIC_HOTKEY_MAP_MAX	64
+#define METHOD_NAME__KMAP	"KMAP"
+static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
+
+static int hkey_map(void)
+{
+	u32 index;
+	acpi_status status;
+	struct acpi_buffer buf;
+	union acpi_object *pack;
+
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	status = acpi_evaluate_object_typed(hkey_handle, METHOD_NAME__KMAP, NULL, &buf, ACPI_TYPE_PACKAGE);
+	if (status != AE_OK) {
+		printk(KERN_ERR ": ACPI exception: %s\n",
+				acpi_format_exception(status));
+		return -1;
+	}
+	pack = buf.pointer;
+	for (index = 0; index < pack->package.count; index++) {
+		union acpi_object *sub_pack = &pack->package.elements[index];
+		union acpi_object *element = &sub_pack->package.elements[0];
+
+		hotkey_keycode_map[index].type = element->integer.value;
+		element = &sub_pack->package.elements[1];
+		hotkey_keycode_map[index].code = element->integer.value;
+		element = &sub_pack->package.elements[2];
+		hotkey_keycode_map[index].keycode = element->integer.value;
+	}
+
+	return 0;
+}
+
+static int hotkey_backlight_set(bool enable)
+{
+	if (!acpi_evalf(hkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
+		return -EIO;
+
+	return 0;
+}
+
+static int __init event_init(struct generic_struct *sub_driver)
+{
+	int ret;
+
+	GENERIC_ACPIHANDLE_INIT(hkey);
+	ret = hkey_map();
+	if (ret) {
+		printk(KERN_ERR "Fail to parse keymap from DSDT.\n");
+		return ret;
+	}
+
+	ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
+	if (ret) {
+		printk(KERN_ERR "Fail to setup input device keymap\n");
+		input_free_device(generic_inputdev);
+
+		return ret;
+	}
+
+	/*
+	 * This hotkey driver handle backlight event when
+	 * acpi_video_get_backlight_type() gets acpi_backlight_vendor
+	 */
+	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
+		hotkey_backlight_set(false);
+	else
+		hotkey_backlight_set(true);
+
+	printk("ACPI:enabling firmware HKEY event interface...\n");
+
+	return ret;
+}
+
+#define GENERIC_EVENT_TYPE_OFF		12
+#define GENERIC_EVENT_MASK		0xFFF
+#define TPACPI_MAX_ACPI_ARGS 3
+
+static int acpi_evalf(acpi_handle handle,
+		      int *res, char *method, char *fmt, ...)
+{
+	char res_type;
+	char *fmt0 = fmt;
+	va_list ap;
+	int success, quiet;
+	acpi_status status;
+	struct acpi_object_list params;
+	struct acpi_buffer result, *resultp;
+	union acpi_object in_objs[TPACPI_MAX_ACPI_ARGS], out_obj;
+
+	if (!*fmt) {
+		pr_err("acpi_evalf() called with empty format\n");
+		return 0;
+	}
+
+	if (*fmt == 'q') {
+		quiet = 1;
+		fmt++;
+	} else
+		quiet = 0;
+
+	res_type = *(fmt++);
+
+	params.count = 0;
+	params.pointer = &in_objs[0];
+
+	va_start(ap, fmt);
+	while (*fmt) {
+		char c = *(fmt++);
+		switch (c) {
+		case 'd':	/* int */
+			in_objs[params.count].integer.value = va_arg(ap, int);
+			in_objs[params.count++].type = ACPI_TYPE_INTEGER;
+			break;
+			/* add more types as needed */
+		default:
+			pr_err("acpi_evalf() called with invalid format character '%c'\n",
+			       c);
+			va_end(ap);
+			return 0;
+		}
+	}
+	va_end(ap);
+
+	if (res_type != 'v') {
+		result.length = sizeof(out_obj);
+		result.pointer = &out_obj;
+		resultp = &result;
+	} else
+		resultp = NULL;
+
+	status = acpi_evaluate_object(handle, method, &params, resultp);
+
+	switch (res_type) {
+	case 'd':		/* int */
+		success = (status == AE_OK &&
+			   out_obj.type == ACPI_TYPE_INTEGER);
+		if (success && res)
+			*res = out_obj.integer.value;
+		break;
+	case 'v':		/* void */
+		success = status == AE_OK;
+		break;
+		/* add more types as needed */
+	default:
+		pr_err("acpi_evalf() called with invalid format character '%c'\n",
+		       res_type);
+		return 0;
+	}
+
+	if (!success && !quiet)
+		pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
+		       method, fmt0, acpi_format_exception(status));
+
+	return success;
+}
+
+int ec_get_brightness(void)
+{
+	int status = 0;
+
+	if (!hkey_handle)
+		return -ENXIO;
+
+	if (!acpi_evalf(hkey_handle, &status, "ECBG", "d"))
+		return -EIO;
+
+	if (status < 0)
+		return status;
+
+	return status;
+}
+EXPORT_SYMBOL(ec_get_brightness);
+
+int ec_set_brightness(int level)
+{
+
+	int ret = 0;
+
+	if (!hkey_handle)
+		return -ENXIO;
+
+	if (!acpi_evalf(hkey_handle, NULL, "ECBS", "vd", level))
+		ret = -EIO;
+
+	return ret;
+}
+EXPORT_SYMBOL(ec_set_brightness);
+
+int ec_backlight_level(u8 level)
+{
+	int status = 0;
+
+	if (!hkey_handle)
+		return -ENXIO;
+
+	if (!acpi_evalf(hkey_handle, &status, "ECLL", "d"))
+		return -EIO;
+
+	if ((status < 0) || (level > status))
+		return status;
+
+	if (!acpi_evalf(hkey_handle, &status, "ECSL", "d"))
+		return -EIO;
+
+	if ((status < 0) || (level < status))
+		return status;
+
+	return level;
+}
+EXPORT_SYMBOL(ec_backlight_level);
+
+static int loongson_laptop_backlight_update(struct backlight_device *bd)
+{
+	int lvl = ec_backlight_level(bd->props.brightness);
+
+	if (lvl < 0)
+		return -EIO;
+	if (ec_set_brightness(lvl))
+		return -EIO;
+
+	return 0;
+}
+
+static int loongson_laptop_get_brightness(struct backlight_device *bd)
+{
+	u8 level;
+
+	level = ec_get_brightness();
+	if (level < 0)
+		return -EIO;
+
+	return level;
+}
+
+static const struct backlight_ops backlight_laptop_ops = {
+	.update_status = loongson_laptop_backlight_update,
+	.get_brightness = loongson_laptop_get_brightness,
+};
+
+static int laptop_backlight_register(void)
+{
+	int status = 0;
+	struct backlight_properties props;
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_PLATFORM;
+
+	if (!acpi_evalf(hkey_handle, &status, "ECLL", "d"))
+		return -EIO;
+
+	props.brightness = 1;
+	props.max_brightness = status;
+
+	backlight_device_register("loongson_laptop",
+				NULL, NULL, &backlight_laptop_ops, &props);
+
+	return 0;
+}
+
+int turn_on_backlight(void)
+{
+	int status;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+
+	arg0.integer.value = 1;
+	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
+	if (ACPI_FAILURE(status)) {
+		pr_info("Loongson lvds error: 0x%x\n", status);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+int turn_off_backlight(void)
+{
+	int status;
+	union acpi_object arg0 = { ACPI_TYPE_INTEGER };
+	struct acpi_object_list args = { 1, &arg0 };
+
+	arg0.integer.value = 0;
+	status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
+	if (ACPI_FAILURE(status)) {
+		pr_info("Loongson lvds error: 0x%x\n", status);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int hotkey_status_get(int *status)
+{
+	if (!acpi_evalf(hkey_handle, status, "GSWS", "d"))
+		return -EIO;
+
+	return 0;
+}
+
+static void event_notify(struct generic_struct *sub_driver, u32 event)
+{
+	struct key_entry *ke = NULL;
+	int scan_code = event & GENERIC_EVENT_MASK;
+	int type = (event >> GENERIC_EVENT_TYPE_OFF) & 0xF;
+
+	ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
+	if (ke) {
+		if (type == KE_SW) {
+			int status = 0;
+
+			if (hotkey_status_get(&status))
+				return;
+
+			ke->sw.value = !!(status & (1 << ke->sw.code));
+		}
+		sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
+	}
+}
+
+static const struct acpi_device_id loongson_htk_device_ids[] = {
+	{LOONGSON_ACPI_HKEY_HID, 0},
+	{"", 0},
+};
+
+static struct generic_acpi_drv_struct ec_event_acpidriver = {
+	.hid = loongson_htk_device_ids,
+	.notify = event_notify,
+	.handle = &hkey_handle,
+	.type = ACPI_DEVICE_NOTIFY,
+};
+
+/****************************************************************************
+ ****************************************************************************
+ *
+ * Infrastructure
+ *
+ ****************************************************************************
+ ****************************************************************************/
+static int __init probe_for_generic(void)
+{
+	if (acpi_disabled)
+		return -ENODEV;
+
+	/* The EC handler is required */
+	generic_acpi_handle_locate("ec", LOONGSON_ACPI_EC_HID, &ec_handle);
+	if (!ec_handle) {
+		pr_err("Not yet supported Loongson Generic Laptop/All-in-one detected!\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void generic_exit(struct generic_struct *sub_driver)
+{
+
+	if (sub_driver->flags.acpi_notify_installed) {
+		BUG_ON(!sub_driver->acpi);
+		acpi_remove_notify_handler(*sub_driver->acpi->handle,
+					   sub_driver->acpi->type,
+					   dispatch_acpi_notify);
+		sub_driver->flags.acpi_notify_installed = 0;
+	}
+
+	if (sub_driver->flags.acpi_driver_registered) {
+		BUG_ON(!sub_driver->acpi);
+		acpi_bus_unregister_driver(sub_driver->acpi->driver);
+		kfree(sub_driver->acpi->driver);
+		sub_driver->acpi->driver = NULL;
+		sub_driver->flags.acpi_driver_registered = 0;
+	}
+
+}
+
+static int __init generic_subdriver_init(struct generic_struct *sub_driver)
+{
+	int ret;
+
+	BUG_ON(sub_driver == NULL);
+
+	if (sub_driver->init)
+		sub_driver->init(sub_driver);
+
+	if (sub_driver->acpi) {
+		if (sub_driver->acpi->hid) {
+			ret = register_generic_subdriver(sub_driver);
+			if (ret)
+				goto err_out;
+		}
+
+		if (sub_driver->acpi->notify) {
+			ret = setup_acpi_notify(sub_driver);
+			if (ret == -ENODEV) {
+				ret = 0;
+				goto err_out;
+			}
+			if (ret < 0)
+				goto err_out;
+		}
+	}
+
+	return 0;
+
+err_out:
+	generic_exit(sub_driver);
+	return (ret < 0) ? ret : 0;
+}
+
+/* Module init, exit, parameters */
+static struct generic_struct generic_sub_drivers[] __initdata = {
+	{
+		.name = "EC Event",
+		.init = event_init,
+		.acpi = &ec_event_acpidriver,
+	},
+};
+
+static void generic_acpi_laptop_exit(void);
+
+static int __init generic_acpi_laptop_init(void)
+{
+	int i, ret, status;
+
+	ret = probe_for_generic();
+	if (ret) {
+		generic_acpi_laptop_exit();
+		return ret;
+	}
+	generic_inputdev = input_allocate_device();
+	if (!generic_inputdev) {
+		pr_err("unable to allocate input device\n");
+		generic_acpi_laptop_exit();
+		return -ENOMEM;
+	}
+
+	/* Prepare input device, but don't register */
+	generic_inputdev->name =
+		"Loongson Generic Laptop/All-in-one Extra Buttons";
+	generic_inputdev->phys = ACPI_LAPTOP_DRVR_NAME "/input0";
+	generic_inputdev->id.bustype = BUS_HOST;
+	generic_inputdev->dev.parent = NULL;
+
+	/* Init subdrivers */
+	for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
+		ret = generic_subdriver_init(&generic_sub_drivers[i]);
+		if (ret < 0) {
+			generic_acpi_laptop_exit();
+			return ret;
+		}
+	}
+
+	ret = input_register_device(generic_inputdev);
+	if (ret < 0) {
+		pr_err("unable to register input device\n");
+		generic_acpi_laptop_exit();
+		return ret;
+	}
+
+	generic_features.input_device_registered = 1;
+
+	if (acpi_evalf(hkey_handle, &status, "ECBG", "d")) {
+		pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
+		ret = laptop_backlight_register();
+		if (ret < 0)
+			pr_err("Loongson Laptop: laptop-backlight device register failed\n");
+	}
+
+	return 0;
+}
+
+static void __exit generic_acpi_laptop_exit(void)
+{
+	if (generic_inputdev) {
+		if (generic_features.input_device_registered)
+			input_unregister_device(generic_inputdev);
+		else
+			input_free_device(generic_inputdev);
+	}
+}
+
+module_init(generic_acpi_laptop_init);
+module_exit(generic_acpi_laptop_exit);
+
+MODULE_ALIAS("platform:acpi-laptop");
+MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
+MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
+MODULE_DESCRIPTION(ACPI_LAPTOP_DESC);
+MODULE_VERSION(ACPI_LAPTOP_VERSION);
+MODULE_LICENSE("GPL");