#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/delay.h>
#include <linux/dev_printk.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/driver.h>
#include <linux/gpio/machine.h>
#include <linux/gpio/property.h>
#include <linux/input-event-codes.h>
#include <linux/property.h>
#define DRIVER_NAME "barco-p50-gpio"
#define P50_GPIO_LINE_LED 0
#define P50_GPIO_LINE_BTN 1
#define P50_GPIO_IO_PORT_BASE 0x299
#define P50_PORT_DATA 0x00
#define P50_PORT_CMD 0x01
#define P50_STATUS_OBF 0x01
#define P50_STATUS_IBF 0x02
#define P50_CMD_READ 0xa0
#define P50_CMD_WRITE 0x50
#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 const struct software_node gpiochip_node = {
.name = DRIVER_NAME,
};
static const struct software_node gpio_leds_node = {
.name = "gpio-leds-identify",
};
static const struct property_entry identify_led_props[] = {
PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_LED, GPIO_ACTIVE_HIGH),
{ }
};
static const struct software_node identify_led_node = {
.parent = &gpio_leds_node,
.name = "identify",
.properties = identify_led_props,
};
static const struct property_entry gpio_keys_props[] = {
PROPERTY_ENTRY_STRING("label", "identify"),
PROPERTY_ENTRY_U32("poll-interval", 100),
{ }
};
static const struct software_node gpio_keys_node = {
.name = "gpio-keys-identify",
.properties = gpio_keys_props,
};
static struct property_entry vendor_key_props[] = {
PROPERTY_ENTRY_U32("linux,code", KEY_VENDOR),
PROPERTY_ENTRY_GPIO("gpios", &gpiochip_node, P50_GPIO_LINE_BTN, GPIO_ACTIVE_LOW),
{ }
};
static const struct software_node vendor_key_node = {
.parent = &gpio_keys_node,
.properties = vendor_key_props,
};
static const struct software_node *p50_swnodes[] = {
&gpiochip_node,
&gpio_leds_node,
&identify_led_node,
&gpio_keys_node,
&vendor_key_node,
NULL
};
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;
inb(p50->base + P50_PORT_DATA);
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;
outb(P50_CMD_WRITE | reg, p50->base + P50_PORT_CMD);
ret = p50_wait_ec(p50, P50_STATUS_IBF, 0);
if (ret)
return ret;
outb(val, p50->base + P50_PORT_DATA);
return 0;
}
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);
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;
}
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 int p50_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
{
struct p50_gpio *p50 = gpiochip_get_data(gc);
int ret;
mutex_lock(&p50->lock);
ret = p50_send_mbox_cmd(p50, P50_MBOX_CMD_WRITE_GPIO,
gpio_params[offset], value);
mutex_unlock(&p50->lock);
return ret;
}
static int p50_gpio_probe(struct platform_device *pdev)
{
struct platform_device_info key_info = {
.name = "gpio-keys-polled",
.id = PLATFORM_DEVID_NONE,
.parent = &pdev->dev,
};
struct platform_device_info led_info = {
.name = "leds-gpio",
.id = PLATFORM_DEVID_NONE,
.parent = &pdev->dev,
};
struct p50_gpio *p50;
struct resource *res;
int ret;
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 = devm_kzalloc(&pdev->dev, sizeof(*p50), GFP_KERNEL);
if (!p50)
return -ENOMEM;
platform_set_drvdata(pdev, p50);
mutex_init(&p50->lock);
p50->base = res->start;
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;
ret = p50_wait_mbox_idle(p50);
if (ret)
return ret;
ret = p50_write_mbox_reg(p50, P50_MBOX_REG_CMD, P50_MBOX_CMD_CLEAR);
if (ret)
return ret;
ret = p50_wait_mbox_idle(p50);
if (ret)
return ret;
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;
}
ret = software_node_register_node_group(p50_swnodes);
if (ret)
return dev_err_probe(&pdev->dev, ret, "failed to register software nodes");
led_info.fwnode = software_node_fwnode(&gpio_leds_node);
p50->leds_pdev = platform_device_register_full(&led_info);
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;
}
key_info.fwnode = software_node_fwnode(&gpio_keys_node);
p50->keys_pdev = platform_device_register_full(&key_info);
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:
software_node_unregister_node_group(p50_swnodes);
return ret;
}
static void 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);
software_node_unregister_node_group(p50_swnodes);
}
static struct platform_driver p50_gpio_driver = {
.driver = {
.name = DRIVER_NAME,
},
.probe = p50_gpio_probe,
.remove = p50_gpio_remove,
};
static const struct dmi_system_id dmi_ids[] __initconst = {
{
.matches = {
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Barco"),
DMI_EXACT_MATCH(DMI_PRODUCT_FAMILY, "P50")
},
},
{}
};
MODULE_DEVICE_TABLE(dmi, dmi_ids);
static int __init p50_module_init(void)
{
struct resource res = DEFINE_RES_IO(P50_GPIO_IO_PORT_BASE, P50_PORT_CMD + 1);
int ret;
if (!dmi_first_match(dmi_ids))
return -ENODEV;
ret = platform_driver_register(&p50_gpio_driver);
if (ret)
return ret;
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");