linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
To: matthew.garrett@nebula.com
Cc: linux-kernel <linux-kernel@vger.kernel.org>,
	Hans de Goede <hdegoede@redhat.com>,
	platform-driver-x86@vger.kernel.org
Subject: Re: [PATCH v2] mba6x_bl: Backlight driver for mid 2013 MacBook Air
Date: Mon, 5 May 2014 16:49:26 +0200	[thread overview]
Message-ID: <CAMeQTsYA0tGQ1SfhNhMctPW6H_y1NGjGXsM5ZcMSwOqE-bApyg@mail.gmail.com> (raw)
In-Reply-To: <1398781312-3607-1-git-send-email-patrik.r.jakobsson@gmail.com>

Hi Matthew,

This seems to be in good shape. Do you have anything to add?
If not, can you pick this patch up through platform-drivers-x86?

Thanks
Patrik

On Tue, Apr 29, 2014 at 4:21 PM, Patrik Jakobsson
<patrik.r.jakobsson@gmail.com> wrote:
> This driver takes control over the LP8550 backlight driver chip found
> in the mid 2013 and newer MacBook Air (6,1 and 6,2). The i915 GPU driver
> cannot properly restore the backlight after resume, but with this driver
> we can hijack the LP8550 and get fully functional backlight support.
>
> v2: - Dropped "if ACPI" in Kconfig since we already depend on it
>     - Added comment about brightness mapping
>     - Removed lp8550_init() from set_brightness()
>     - Always write to dev_ctl when setting brightness
>     - Change %Ld to standard C %lld
>     - Constify the backlight_ops struct
>
> Signed-off-by: Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
> ---
>  MAINTAINERS                     |   6 +
>  drivers/platform/x86/Kconfig    |  13 ++
>  drivers/platform/x86/Makefile   |   1 +
>  drivers/platform/x86/mba6x_bl.c | 353 ++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 373 insertions(+)
>  create mode 100644 drivers/platform/x86/mba6x_bl.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e67ea24..cad3e82 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5576,6 +5576,12 @@ T:       git git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac80211-next.git
>  S:     Maintained
>  F:     net/mac80211/rc80211_pid*
>
> +MACBOOK AIR 6,X BACKLIGHT DRIVER
> +M:     Patrik Jakobsson <patrik.r.jakobsson@gmail.com>
> +L:     platform-driver-x86@vger.kernel.org
> +S:     Maintained
> +F:     drivers/platform/x86/mba6x_bl.c
> +
>  MACVLAN DRIVER
>  M:     Patrick McHardy <kaber@trash.net>
>  L:     netdev@vger.kernel.org
> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index 27df2c5..10ac918 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -795,6 +795,19 @@ config APPLE_GMUX
>           graphics as well as the backlight. Currently only backlight
>           control is supported by the driver.
>
> +config MBA6X_BL
> +       tristate "MacBook Air 6,x backlight driver"
> +       depends on ACPI
> +       depends on BACKLIGHT_CLASS_DEVICE
> +       select ACPI_VIDEO
> +       help
> +        This driver takes control over the LP8550 backlight driver found in
> +        some MacBook Air models. Say Y here if you have a MacBook Air from mid
> +        2013 or newer.
> +
> +        To compile this driver as a module, choose M here: the module will
> +        be called mba6x_bl.
> +
>  config INTEL_RST
>          tristate "Intel Rapid Start Technology Driver"
>         depends on ACPI
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 1a2eafc..9a182fe 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -56,3 +56,4 @@ obj-$(CONFIG_INTEL_SMARTCONNECT)      += intel-smartconnect.o
>
>  obj-$(CONFIG_PVPANIC)           += pvpanic.o
>  obj-$(CONFIG_ALIENWARE_WMI)    += alienware-wmi.o
> +obj-$(CONFIG_MBA6X_BL)         += mba6x_bl.o
> diff --git a/drivers/platform/x86/mba6x_bl.c b/drivers/platform/x86/mba6x_bl.c
> new file mode 100644
> index 0000000..c549667
> --- /dev/null
> +++ b/drivers/platform/x86/mba6x_bl.c
> @@ -0,0 +1,353 @@
> +/*
> + * MacBook Air 6,1 and 6,2 (mid 2013) backlight driver
> + *
> + * Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 as published by
> + * the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/backlight.h>
> +#include <linux/acpi.h>
> +#include <acpi/acpi.h>
> +#include <acpi/video.h>
> +
> +#define LP8550_SMBUS_ADDR      (0x58 >> 1)
> +#define LP8550_REG_BRIGHTNESS  0
> +#define LP8550_REG_DEV_CTL     1
> +#define LP8550_REG_FAULT       2
> +#define LP8550_REG_IDENT       3
> +#define LP8550_REG_DIRECT_CTL  4
> +#define LP8550_REG_TEMP_MSB    5 /* Must be read before TEMP_LSB */
> +#define LP8550_REG_TEMP_LSB    6
> +
> +#define INIT_BRIGHTNESS                150
> +
> +static struct {
> +       u8 brightness;  /* Brightness control */
> +       u8 dev_ctl;     /* Device control */
> +       u8 fault;       /* Fault indication */
> +       u8 ident;       /* Identification */
> +       u8 direct_ctl;  /* Direct control */
> +       u8 temp_msb;    /* Temperature MSB  */
> +       u8 temp_lsb;    /* Temperature LSB */
> +} lp8550_regs;
> +
> +static struct platform_device *platform_device;
> +static struct backlight_device *backlight_device;
> +
> +static int lp8550_reg_read(u8 reg, u8 *val)
> +{
> +       acpi_status status;
> +       acpi_handle handle;
> +       struct acpi_object_list arg_list;
> +       struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> +       union acpi_object args[2];
> +       union acpi_object *result;
> +       int ret = 0;
> +
> +       status = acpi_get_handle(NULL, "\\_SB.PCI0.SBUS.SRDB", &handle);
> +       if (ACPI_FAILURE(status)) {
> +               pr_debug("mba6x_bl: Failed to get acpi handle\n");
> +               ret = -ENODEV;
> +               goto out;
> +       }
> +
> +       args[0].type = ACPI_TYPE_INTEGER;
> +       args[0].integer.value = (LP8550_SMBUS_ADDR << 1) | 1;
> +
> +       args[1].type = ACPI_TYPE_INTEGER;
> +       args[1].integer.value = reg;
> +
> +       arg_list.count = 2;
> +       arg_list.pointer = args;
> +
> +       status = acpi_evaluate_object(handle, NULL, &arg_list, &buffer);
> +       if (ACPI_FAILURE(status)) {
> +               pr_debug("mba6x_bl: Failed to read reg: 0x%x\n", reg);
> +               ret = -ENODEV;
> +               goto out;
> +       }
> +
> +       result = buffer.pointer;
> +
> +       if (result->type != ACPI_TYPE_INTEGER) {
> +               pr_debug("mba6x_bl: Invalid response in reg: 0x%x (len: %Ld)\n",
> +                        reg, buffer.length);
> +               ret = -EINVAL;
> +               goto out;
> +       }
> +
> +       *val = (u8)result->integer.value;
> +out:
> +       kfree(buffer.pointer);
> +       return ret;
> +}
> +
> +static int lp8550_reg_write(u8 reg, u8 val)
> +{
> +       acpi_status status;
> +       acpi_handle handle;
> +       struct acpi_object_list arg_list;
> +       struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
> +       union acpi_object args[3];
> +       union acpi_object *result;
> +       int ret = 0;
> +
> +       status = acpi_get_handle(NULL, "\\_SB.PCI0.SBUS.SWRB", &handle);
> +       if (ACPI_FAILURE(status)) {
> +               pr_debug("mba6x_bl: Failed to get acpi handle\n");
> +               ret = -ENODEV;
> +               goto out;
> +       }
> +
> +       args[0].type = ACPI_TYPE_INTEGER;
> +       args[0].integer.value = (LP8550_SMBUS_ADDR << 1) & ~1;
> +
> +       args[1].type = ACPI_TYPE_INTEGER;
> +       args[1].integer.value = reg;
> +
> +       args[2].type = ACPI_TYPE_INTEGER;
> +       args[2].integer.value = val;
> +
> +       arg_list.count = 3;
> +       arg_list.pointer = args;
> +
> +       status = acpi_evaluate_object(handle, NULL, &arg_list, &buffer);
> +       if (ACPI_FAILURE(status)) {
> +               pr_debug("mba6x_bl: Failed to write reg: 0x%x\n", reg);
> +               ret = -ENODEV;
> +               goto out;
> +       }
> +
> +       result = buffer.pointer;
> +
> +       if (result->type != ACPI_TYPE_INTEGER || result->integer.value != 1) {
> +               pr_debug("mba6x_bl: Invalid response at reg: 0x%x (len: %lld)\n",
> +                        reg, buffer.length);
> +               ret = -EINVAL;
> +               goto out;
> +       }
> +
> +out:
> +       kfree(buffer.pointer);
> +       return ret;
> +}
> +
> +/*
> + * Map the brightness value to an exponential curve. This makes the perceived
> + * brightness more linear and allows to make better use of the available
> + * brightness range.
> + */
> +static inline int map_brightness(int b)
> +{
> +       return ((b * b + 254) / 255);
> +}
> +
> +static int set_brightness(int brightness)
> +{
> +       int ret;
> +
> +       if (brightness < 0 || brightness > 255)
> +               return -EINVAL;
> +
> +       brightness = map_brightness(brightness);
> +       ret = lp8550_reg_write(LP8550_REG_BRIGHTNESS, (u8)brightness);
> +       if (ret)
> +               return ret;
> +
> +       ret = lp8550_reg_write(LP8550_REG_DEV_CTL, 0x05);
> +       return ret;
> +}
> +
> +static int get_brightness(struct backlight_device *bdev)
> +{
> +       return bdev->props.brightness;
> +}
> +
> +static int update_status(struct backlight_device *bdev)
> +{
> +       return set_brightness(bdev->props.brightness);
> +}
> +
> +static int lp8550_probe(void)
> +{
> +       u8 val;
> +       int ret;
> +
> +       ret = lp8550_reg_read(LP8550_REG_IDENT, &val);
> +       if (ret)
> +               return ret;
> +
> +       if (val != 0xfc)
> +               return -ENODEV;
> +
> +       pr_info("mba6x_bl: Found LP8550 backlight driver\n");
> +       return 0;
> +}
> +
> +static int lp8550_save(void)
> +{
> +       int ret;
> +
> +       ret = lp8550_reg_read(LP8550_REG_DEV_CTL, &lp8550_regs.dev_ctl);
> +       if (ret)
> +               return ret;
> +
> +       ret = lp8550_reg_read(LP8550_REG_BRIGHTNESS, &lp8550_regs.brightness);
> +       return ret;
> +}
> +
> +
> +static int lp8550_restore(void)
> +{
> +       int ret;
> +
> +       ret = lp8550_reg_write(LP8550_REG_BRIGHTNESS, lp8550_regs.brightness);
> +       if (ret)
> +               return ret;
> +
> +       ret = lp8550_reg_write(LP8550_REG_DEV_CTL, lp8550_regs.dev_ctl);
> +       return ret;
> +}
> +
> +static int lp8550_init(void)
> +{
> +       int ret, i;
> +
> +       for (i = 0; i < 10; i++) {
> +               ret = lp8550_reg_write(LP8550_REG_BRIGHTNESS, INIT_BRIGHTNESS);
> +               if (!ret)
> +                       break;
> +       }
> +
> +       if (i > 0)
> +               pr_err("mba6x_bl: Init retries: %d\n", i);
> +
> +       if (ret)
> +               return ret;
> +
> +       ret = lp8550_reg_write(LP8550_REG_DEV_CTL, 0x05);
> +       return ret;
> +}
> +
> +static const struct backlight_ops backlight_ops = {
> +       .update_status = update_status,
> +       .get_brightness = get_brightness,
> +};
> +
> +static struct platform_driver drv;
> +
> +static int platform_probe(struct platform_device *dev)
> +{
> +       struct backlight_properties props;
> +       int ret;
> +
> +       ret = lp8550_probe();
> +       if (ret)
> +               return ret;
> +
> +       ret = lp8550_save();
> +       if (ret)
> +               return ret;
> +
> +       ret = lp8550_init();
> +       if (ret)
> +               return ret;
> +
> +       memset(&props, 0, sizeof(struct backlight_properties));
> +       props.max_brightness = 255;
> +       props.brightness = INIT_BRIGHTNESS;
> +       props.type = BACKLIGHT_FIRMWARE;
> +
> +       backlight_device = backlight_device_register("mba6x_backlight",
> +                                                    NULL, NULL,
> +                                                    &backlight_ops, &props);
> +       if (IS_ERR(backlight_device)) {
> +               pr_err("mba6x_bl: Failed to register backlight device\n");
> +               return PTR_ERR(backlight_device);
> +       }
> +
> +       acpi_video_dmi_promote_vendor();
> +       acpi_video_unregister();
> +
> +       backlight_update_status(backlight_device);
> +
> +       return 0;
> +}
> +
> +static int platform_remove(struct platform_device *dev)
> +{
> +       backlight_device_unregister(backlight_device);
> +       lp8550_restore();
> +       pr_info("mba6x_bl: Restored old configuration\n");
> +
> +       return 0;
> +}
> +
> +static int platform_resume(struct platform_device *dev)
> +{
> +       /*
> +        * Firmware restores the LP8550 for us but we might need some tweaking
> +        * in the future if firmware behaviour is changed.
> +        */
> +       return 0;
> +}
> +
> +static void platform_shutdown(struct platform_device *dev)
> +{
> +       /* We must restore or screen might go black on reboot */
> +       lp8550_restore();
> +}
> +
> +static struct platform_driver drv = {
> +       .probe          = platform_probe,
> +       .remove         = platform_remove,
> +       .resume         = platform_resume,
> +       .shutdown       = platform_shutdown,
> +       .driver = {
> +               .name = "mba6x_bl",
> +               .owner = THIS_MODULE,
> +       },
> +};
> +
> +static int __init mba6x_bl_init(void)
> +{
> +       int ret;
> +
> +       ret = platform_driver_register(&drv);
> +       if (ret) {
> +               pr_err("Failed to register platform driver\n");
> +               return ret;
> +       }
> +
> +       platform_device = platform_device_register_simple("mba6x_bl", -1, NULL,
> +                                                         0);
> +       return 0;
> +}
> +
> +static void __exit mba6x_bl_exit(void)
> +{
> +       platform_device_unregister(platform_device);
> +       platform_driver_unregister(&drv);
> +}
> +
> +module_init(mba6x_bl_init);
> +module_exit(mba6x_bl_exit);
> +
> +MODULE_AUTHOR("Patrik Jakobsson <patrik.r.jakobsson@gmail.com>");
> +MODULE_DESCRIPTION("MacBook Air 6,1 and 6,2 backlight driver");
> +MODULE_LICENSE("GPL");
> +
> +MODULE_ALIAS("dmi:*:pnMacBookAir6*");
> --
> 1.9.1
>

  parent reply	other threads:[~2014-05-05 14:49 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-04-29 14:21 [PATCH v2] mba6x_bl: Backlight driver for mid 2013 MacBook Air Patrik Jakobsson
2014-04-30  8:19 ` Hans de Goede
2014-05-05 14:49 ` Patrik Jakobsson [this message]
2014-05-05 18:28   ` Matthew Garrett
2014-05-05 20:26     ` Patrik Jakobsson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAMeQTsYA0tGQ1SfhNhMctPW6H_y1NGjGXsM5ZcMSwOqE-bApyg@mail.gmail.com \
    --to=patrik.r.jakobsson@gmail.com \
    --cc=hdegoede@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=matthew.garrett@nebula.com \
    --cc=platform-driver-x86@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).