All of lore.kernel.org
 help / color / mirror / Atom feed
From: Santosh Kumar Yadav <santoshkumar.yadav@barco.com>
To: <santoshyadav30@gmail.com>
Cc: <santoshkumar.yadav@barco.com>,
	Peter Korsgaard <peter@korsgaard.com>,
	Hans de Goede <hdegoede@redhat.com>,
	Mark Gross <mgross@linux.intel.com>,
	Peter Korsgaard <peter.korsgaard@barco.com>,
	<linux-kernel@vger.kernel.org>,
	<platform-driver-x86@vger.kernel.org>
Subject: [PATCH] platform/x86: Support for EC-connected GPIOs for identify LED/button on Barco P50 board
Date: Wed, 13 Oct 2021 19:33:55 +0530	[thread overview]
Message-ID: <20211013140356.6235-1-santoshkumar.yadav@barco.com> (raw)

Add a driver providing access to the GPIOs for the identify button and led
present on Barco P50 board, based on the pcengines-apuv2.c driver.

There is unfortunately no suitable ACPI entry for the EC communication
interface, so instead bind to boards with "P50" as their DMI product family
and hard code the I/O port number (0x299).

The driver also hooks up the leds-gpio and gpio-keys-polled drivers to the
GPIOs, so they are finally exposed as:

LED:
/sys/class/leds/identify

Button: (/proc/bus/input/devices)
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="identify"
P: Phys=gpio-keys-polled/input0
S: Sysfs=/devices/platform/barco-p50-gpio/gpio-keys-polled/input/input10
U: Uniq=
H: Handlers=event10
B: PROP=0
B: EV=3
B: KEY=1000000 0 0 0 0 0 0

Signed-off-by: Santosh Kumar Yadav <santoshkumar.yadav@barco.com>
Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
---
 MAINTAINERS                           |   6 +
 drivers/platform/x86/Kconfig          |  10 +
 drivers/platform/x86/Makefile         |   3 +
 drivers/platform/x86/barco-p50-gpio.c | 427 ++++++++++++++++++++++++++
 4 files changed, 446 insertions(+)
 create mode 100644 drivers/platform/x86/barco-p50-gpio.c

diff --git a/MAINTAINERS b/MAINTAINERS
index a4a0c2baaf27..604f544a9e17 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3220,6 +3220,12 @@ F:       drivers/video/backlight/
 F:     include/linux/backlight.h
 F:     include/linux/pwm_backlight.h

+BARCO P50 GPIO DRIVER
+M:     Santosh Kumar Yadav <santoshkumar.yadav@barco.com>
+M:     Peter Korsgaard <peter.korsgaard@barco.com>
+S:     Maintained
+F:     drivers/platform/x86/barco-p50-gpio.c
+
 BATMAN ADVANCED
 M:     Marek Lindner <mareklindner@neomailbox.ch>
 M:     Simon Wunderlich <sw@simonwunderlich.de>
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index e21ea3d23e6f..42b4895e4acc 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -713,6 +713,16 @@ config PCENGINES_APU2
          To compile this driver as a module, choose M here: the module
          will be called pcengines-apuv2.

+config BARCO_P50_GPIO
+       tristate "Barco P50 GPIO driver for identify LED/button"
+       depends on GPIOLIB
+       help
+         This driver provides access to the GPIOs for the identify button
+         and led present on Barco P50 board.
+
+         To compile this driver as a module, choose M here: the module
+         will be called barco-p50-gpio.
+
 config SAMSUNG_LAPTOP
        tristate "Samsung Laptop driver"
        depends on RFKILL || RFKILL = n
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 69690e26bb6d..e1acfc03301f 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -80,6 +80,9 @@ obj-$(CONFIG_XO1_RFKILL)      += xo1-rfkill.o
 # PC Engines
 obj-$(CONFIG_PCENGINES_APU2)   += pcengines-apuv2.o

+# Barco
+obj-$(CONFIG_BARCO_P50_GPIO)    += barco-p50-gpio.o
+
 # Samsung
 obj-$(CONFIG_SAMSUNG_LAPTOP)   += samsung-laptop.o
 obj-$(CONFIG_SAMSUNG_Q10)      += samsung-q10.o
diff --git a/drivers/platform/x86/barco-p50-gpio.c b/drivers/platform/x86/barco-p50-gpio.c
new file mode 100644
index 000000000000..1629cfbb85db
--- /dev/null
+++ b/drivers/platform/x86/barco-p50-gpio.c
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/*
+ * Support for EC-connected GPIOs for identify
+ * LED/button on Barco P50 board
+ *
+ * Copyright (C) 2021 Barco NV
+ * Author: Santosh Kumar Yadav <santoshkumar.yadav@barco.com>
+ */
+
+#define pr_fmt(fmt)    KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio_keys.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+#include <linux/input.h>
+
+
+#define DRIVER_NAME    "barco-p50-gpio"
+
+/* GPIO lines */
+#define P50_GPIO_LINE_LED      0
+#define P50_GPIO_LINE_BTN      1
+
+/* GPIO IO Ports */
+#define P50_GPIO_IO_PORT_BASE  0x299
+
+#define P50_PORT_DATA  0x00
+#define P50_PORT_CMD   0x01
+
+#define P50_STATUS_OBF 0x01 /* EC output buffer full */
+#define P50_STATUS_IBF 0x02 /* EC input buffer full */
+
+#define P50_CMD_READ   0xa0
+#define P50_CMD_WRITE  0x50
+
+/* EC mailbox registers */
+#define P50_MBOX_REG_CMD       0x00
+#define P50_MBOX_REG_STATUS    0x01
+#define P50_MBOX_REG_PARAM     0x02
+#define P50_MBOX_REG_DATA      0x03
+
+#define P50_MBOX_CMD_READ_GPIO 0x11
+#define P50_MBOX_CMD_WRITE_GPIO        0x12
+#define P50_MBOX_CMD_CLEAR     0xff
+
+#define P50_MBOX_STATUS_SUCCESS        0x01
+
+#define P50_MBOX_PARAM_LED     0x12
+#define P50_MBOX_PARAM_BTN     0x13
+
+
+struct p50_gpio {
+       struct gpio_chip gc;
+       struct mutex lock;
+       unsigned long base;
+       struct platform_device *leds_pdev;
+       struct platform_device *keys_pdev;
+};
+
+static struct platform_device *gpio_pdev;
+
+static int gpio_params[] = {
+       [P50_GPIO_LINE_LED] = P50_MBOX_PARAM_LED,
+       [P50_GPIO_LINE_BTN] = P50_MBOX_PARAM_BTN,
+};
+
+static const char * const gpio_names[] = {
+       [P50_GPIO_LINE_LED] = "identify-led",
+       [P50_GPIO_LINE_BTN] = "identify-button",
+};
+
+
+static struct gpiod_lookup_table p50_gpio_led_table = {
+       .dev_id = "leds-gpio",
+       .table = {
+               GPIO_LOOKUP_IDX(DRIVER_NAME, P50_GPIO_LINE_LED, NULL, 0, GPIO_ACTIVE_HIGH),
+               {}
+       }
+};
+
+
+/* low level access routines */
+
+static int p50_wait_ec(struct p50_gpio *p50, int mask, int expected)
+{
+       int i, val;
+
+       for (i = 0; i < 100; i++) {
+               val = inb(p50->base + P50_PORT_CMD) & mask;
+               if (val == expected)
+                       return 0;
+               usleep_range(500, 2000);
+       }
+
+       dev_err(p50->gc.parent, "Timed out waiting for EC (0x%x)\n", val);
+       return -ETIMEDOUT;
+}
+
+
+static int p50_read_mbox_reg(struct p50_gpio *p50, int reg)
+{
+       int ret;
+
+       ret = p50_wait_ec(p50, P50_STATUS_IBF, 0);
+       if (ret)
+               return ret;
+
+       /* clear output buffer flag, prevent unfinished commands */
+       inb(p50->base + P50_PORT_DATA);
+
+       /* cmd/address */
+       outb(P50_CMD_READ | reg, p50->base + P50_PORT_CMD);
+
+       ret = p50_wait_ec(p50, P50_STATUS_OBF, P50_STATUS_OBF);
+       if (ret)
+               return ret;
+
+       return inb(p50->base + P50_PORT_DATA);
+}
+
+static int p50_write_mbox_reg(struct p50_gpio *p50, int reg, int val)
+{
+       int ret;
+
+       ret = p50_wait_ec(p50, P50_STATUS_IBF, 0);
+       if (ret)
+               return ret;
+
+       /* cmd/address */
+       outb(P50_CMD_WRITE | reg, p50->base + P50_PORT_CMD);
+
+       ret = p50_wait_ec(p50, P50_STATUS_IBF, 0);
+       if (ret)
+               return ret;
+
+       /* data */
+       outb(val, p50->base + P50_PORT_DATA);
+
+       return 0;
+}
+
+
+/* mbox routines */
+
+static int p50_wait_mbox_idle(struct p50_gpio *p50)
+{
+       int i, val;
+
+       for (i = 0; i < 1000; i++) {
+               val = p50_read_mbox_reg(p50, P50_MBOX_REG_CMD);
+               /* cmd is 0 when idle */
+               if (val <= 0)
+                       return val;
+
+               usleep_range(500, 2000);
+       }
+
+       dev_err(p50->gc.parent, "Timed out waiting for EC mbox idle (CMD: 0x%x)\n", val);
+
+       return -ETIMEDOUT;
+}
+
+static int p50_send_mbox_cmd(struct p50_gpio *p50, int cmd, int param, int data)
+{
+       int ret;
+
+       ret = p50_wait_mbox_idle(p50);
+       if (ret)
+               return ret;
+
+       ret = p50_write_mbox_reg(p50, P50_MBOX_REG_DATA, data);
+       if (ret)
+               return ret;
+
+       ret = p50_write_mbox_reg(p50, P50_MBOX_REG_PARAM, param);
+       if (ret)
+               return ret;
+
+       ret = p50_write_mbox_reg(p50, P50_MBOX_REG_CMD, cmd);
+       if (ret)
+               return ret;
+
+       ret = p50_wait_mbox_idle(p50);
+       if (ret)
+               return ret;
+
+       ret = p50_read_mbox_reg(p50, P50_MBOX_REG_STATUS);
+       if (ret < 0)
+               return ret;
+
+       if (ret == P50_MBOX_STATUS_SUCCESS)
+               return 0;
+
+       dev_err(p50->gc.parent, "Mbox command failed (CMD=0x%x STAT=0x%x PARAM=0x%x DATA=0x%x)\n",
+               cmd, ret, param, data);
+
+       return -EIO;
+}
+
+
+/* gpio routines */
+
+static int p50_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+       switch (offset) {
+       case P50_GPIO_LINE_BTN:
+               return GPIO_LINE_DIRECTION_IN;
+
+       case P50_GPIO_LINE_LED:
+               return GPIO_LINE_DIRECTION_OUT;
+
+       default:
+               return -EINVAL;
+       }
+}
+
+static int p50_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+       struct p50_gpio *p50 = gpiochip_get_data(gc);
+       int ret;
+
+       mutex_lock(&p50->lock);
+
+       ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_READ_GPIO, gpio_params[offset], 0);
+       if (ret == 0)
+               ret = p50_read_mbox_reg(p50, P50_MBOX_REG_DATA);
+
+       mutex_unlock(&p50->lock);
+
+       return ret;
+}
+
+static void p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+       struct p50_gpio *p50 = gpiochip_get_data(gc);
+
+       mutex_lock(&p50->lock);
+
+       p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO, gpio_params[offset], value);
+
+       mutex_unlock(&p50->lock);
+}
+
+static int p50_gpio_probe(struct platform_device *pdev)
+{
+       /* GPIO LEDs */
+       struct gpio_led leds[] = {
+               { .name = "identify" }
+       };
+
+       struct gpio_led_platform_data leds_pdata = {
+               .num_leds = ARRAY_SIZE(leds),
+               .leds = leds,
+       };
+
+       /* GPIO keyboard */
+       struct gpio_keys_button buttons[] = {
+               {
+                       .code = KEY_RESTART,
+                       .gpio = P50_GPIO_LINE_BTN,
+                       .active_low = 1,
+                       .type = EV_KEY,
+                       .value = 1,
+               },
+       };
+
+       struct gpio_keys_platform_data keys_pdata = {
+               .buttons = buttons,
+               .nbuttons = ARRAY_SIZE(buttons),
+               .poll_interval = 100,
+               .rep = 0,
+               .name = "identify",
+       };
+
+       struct p50_gpio *p50;
+       struct resource *res;
+       int ret;
+
+       p50 = devm_kzalloc(&pdev->dev, sizeof(*p50), GFP_KERNEL);
+       if (!p50)
+               return -ENOMEM;
+
+       res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+       if (!res) {
+               dev_err(&pdev->dev, "Cannot get I/O ports\n");
+               return -ENODEV;
+       }
+
+       if (!devm_request_region(&pdev->dev, res->start, resource_size(res), pdev->name)) {
+               dev_err(&pdev->dev, "Unable to reserve I/O region\n");
+               return -EBUSY;
+       }
+
+       p50->base = res->start;
+
+       ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_CLEAR, 0, 0);
+       if (ret < 0)
+               return -ENODEV;
+
+       p50->gc.owner = THIS_MODULE;
+       p50->gc.parent = &pdev->dev;
+       p50->gc.label = dev_name(&pdev->dev);
+       p50->gc.ngpio = ARRAY_SIZE(gpio_names);
+       p50->gc.names = gpio_names;
+       p50->gc.can_sleep = true;
+       p50->gc.base = -1;
+       p50->gc.get_direction = p50_gpio_get_direction;
+       p50->gc.get = p50_gpio_get;
+       p50->gc.set = p50_gpio_set;
+
+       mutex_init(&p50->lock);
+
+       platform_set_drvdata(pdev, p50);
+
+       ret = devm_gpiochip_add_data(&pdev->dev, &p50->gc, p50);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "Could not register gpiochip: %d\n", ret);
+               return ret;
+       }
+
+       gpiod_add_lookup_table(&p50_gpio_led_table);
+
+       p50->leds_pdev = platform_device_register_data(&pdev->dev,
+               "leds-gpio", PLATFORM_DEVID_NONE, &leds_pdata, sizeof(leds_pdata));
+
+       if (IS_ERR(p50->leds_pdev)) {
+               ret = PTR_ERR(p50->leds_pdev);
+               dev_err(&pdev->dev, "Could not register leds-gpio: %d\n", ret);
+               goto err_leds;
+       }
+
+       /* gpio-keys-polled uses old-style gpio interface, pass the right identifier */
+       buttons[0].gpio += p50->gc.base;
+
+       p50->keys_pdev =
+               platform_device_register_data(&pdev->dev, "gpio-keys-polled",
+                                             PLATFORM_DEVID_NONE,
+                                             &keys_pdata, sizeof(keys_pdata));
+
+       if (IS_ERR(p50->keys_pdev)) {
+               ret = PTR_ERR(p50->keys_pdev);
+               dev_err(&pdev->dev, "Could not register gpio-keys-polled: %d\n", ret);
+               goto err_keys;
+       }
+
+       return 0;
+
+err_keys:
+       platform_device_unregister(p50->leds_pdev);
+err_leds:
+       gpiod_remove_lookup_table(&p50_gpio_led_table);
+
+       return ret;
+}
+
+static int p50_gpio_remove(struct platform_device *pdev)
+{
+       struct p50_gpio *p50 = platform_get_drvdata(pdev);
+
+       platform_device_unregister(p50->keys_pdev);
+       platform_device_unregister(p50->leds_pdev);
+
+       gpiod_remove_lookup_table(&p50_gpio_led_table);
+
+       return 0;
+}
+
+static struct platform_driver p50_gpio_driver = {
+       .driver = {
+               .name = DRIVER_NAME,
+       },
+       .probe = p50_gpio_probe,
+       .remove = p50_gpio_remove,
+};
+
+/* Board setup */
+static const struct dmi_system_id dmi_ids[] __initconst = {
+       {
+               .matches = {
+                       DMI_EXACT_MATCH(DMI_PRODUCT_FAMILY, "P50")
+               },
+       },
+       {}
+};
+
+static int __init p50_module_init(void)
+{
+       struct resource res = DEFINE_RES_IO(P50_GPIO_IO_PORT_BASE, P50_PORT_CMD + 1);
+
+       if (!dmi_first_match(dmi_ids))
+               return -ENODEV;
+
+       platform_driver_register(&p50_gpio_driver);
+
+       gpio_pdev = platform_device_register_simple(DRIVER_NAME, PLATFORM_DEVID_NONE, &res, 1);
+       if (IS_ERR(gpio_pdev)) {
+               pr_err("failed registering %s: %ld\n", DRIVER_NAME, PTR_ERR(gpio_pdev));
+               platform_driver_unregister(&p50_gpio_driver);
+               return PTR_ERR(gpio_pdev);
+       }
+
+       return 0;
+}
+
+static void __exit p50_module_exit(void)
+{
+       platform_device_unregister(gpio_pdev);
+       platform_driver_unregister(&p50_gpio_driver);
+}
+
+module_init(p50_module_init);
+module_exit(p50_module_exit);
+
+MODULE_AUTHOR("Santosh Kumar Yadav, Barco NV <santoshkumar.yadav@barco.com>");
+MODULE_DESCRIPTION("Barco P50 identify GPIOs driver");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: platform:leds-gpio platform:gpio-keys-polled");
--
2.19.1

This message is subject to the following terms and conditions: MAIL DISCLAIMER<http://www.barco.com/en/maildisclaimer>

             reply	other threads:[~2021-10-13 14:10 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-10-13 14:03 Santosh Kumar Yadav [this message]
2021-10-19 14:50 ` [PATCH] platform/x86: Support for EC-connected GPIOs for identify LED/button on Barco P50 board Hans de Goede
2021-10-19 15:14   ` Peter Korsgaard
2021-10-20 12:36 ` [PATCH v2] " Peter Korsgaard
2021-10-20 14:39   ` Hans de Goede
2021-10-22 12:46     ` [PATCH] platform/x86: barco-p50-gpio: use KEY_VENDOR for button insted of KEY_RESTART Peter Korsgaard
2021-10-22 15:40       ` Peter Korsgaard
2021-10-22 16:00         ` Hans de Goede
2021-10-24  8:19       ` Hans de Goede

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=20211013140356.6235-1-santoshkumar.yadav@barco.com \
    --to=santoshkumar.yadav@barco.com \
    --cc=hdegoede@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mgross@linux.intel.com \
    --cc=peter.korsgaard@barco.com \
    --cc=peter@korsgaard.com \
    --cc=platform-driver-x86@vger.kernel.org \
    --cc=santoshyadav30@gmail.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.