206 lines
5.0 KiB
C
206 lines
5.0 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* AAEON GPIO driver
|
|
* Copyright (c) 2021, AAEON Ltd.
|
|
*
|
|
* Author: Edward Lin <edward1_lin@aaeon.com.tw>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*/
|
|
#include <linux/acpi.h>
|
|
#include <linux/bitops.h>
|
|
#include <linux/gpio/driver.h>
|
|
#include <linux/init.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_data/x86/asus-wmi.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#define DRVNAME "gpio_aaeon"
|
|
#define ASUS_NB_WMI_EVENT_GUID "0B3CBB35-E3C2-45ED-91C2-4C5A6D195D1C"
|
|
#define AAEON_WMI_MGMT_GUID "97845ED0-4E6D-11DE-8A39-0800200C9A66"
|
|
|
|
#define GET_GPIO_NUMBER_ID 0x00010000
|
|
#define GET_LEVEL_METHOD_ID 0x00010001
|
|
#define SET_LEVEL_METHOD_ID 0x00010002
|
|
#define GET_DIRECTION_METHOD_ID 0x00010003
|
|
#define SET_DIRECTION_METHOD_ID 0x00010004
|
|
#define GET_SIO_NUMBER_METHOD_ID 0xF0010
|
|
|
|
struct aaeon_gpio_bank {
|
|
struct gpio_chip chip;
|
|
unsigned int regbase;
|
|
struct aaeon_gpio_data *data;
|
|
};
|
|
|
|
struct aaeon_gpio_data {
|
|
int nr_bank;
|
|
struct aaeon_gpio_bank *bank;
|
|
};
|
|
|
|
static int aaeon_gpio_get_number(void);
|
|
static int aaeon_gpio_get_direction(struct gpio_chip *chip,
|
|
unsigned int offset);
|
|
static int aaeon_gpio_output_set_direction(struct gpio_chip *chip,
|
|
unsigned int offset, int value);
|
|
static int aaeon_gpio_input_set_direction(struct gpio_chip *chip,
|
|
unsigned int offset);
|
|
static int aaeon_gpio_get(struct gpio_chip *chip,
|
|
unsigned int offset);
|
|
static void aaeon_gpio_set(struct gpio_chip *chip, unsigned int offset,
|
|
int value);
|
|
|
|
#define AAEON_GPIO_BANK(_base, _ngpio, _regbase) \
|
|
{ \
|
|
.chip = { \
|
|
.label = DRVNAME, \
|
|
.owner = THIS_MODULE, \
|
|
.get_direction = aaeon_gpio_get_direction, \
|
|
.direction_input = aaeon_gpio_input_set_direction, \
|
|
.direction_output = aaeon_gpio_output_set_direction, \
|
|
.get = aaeon_gpio_get, \
|
|
.set = aaeon_gpio_set, \
|
|
.base = _base, \
|
|
.ngpio = _ngpio, \
|
|
.can_sleep = true, \
|
|
}, \
|
|
.regbase = _regbase, \
|
|
}
|
|
|
|
static struct aaeon_gpio_bank aaeon_gpio_bank[] = {
|
|
AAEON_GPIO_BANK(0, 0, 0xF0),
|
|
};
|
|
|
|
static int aaeon_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
|
|
{
|
|
int err, retval;
|
|
u32 dev_id = 0x0;
|
|
|
|
dev_id |= offset;
|
|
err = asus_wmi_evaluate_method(GET_DIRECTION_METHOD_ID, dev_id,
|
|
0, &retval);
|
|
if (err)
|
|
return err;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int aaeon_gpio_input_set_direction(struct gpio_chip *chip,
|
|
unsigned int offset)
|
|
{
|
|
int err, retval;
|
|
u32 dev_id;
|
|
|
|
dev_id = BIT(16) | offset;
|
|
err = asus_wmi_evaluate_method(SET_DIRECTION_METHOD_ID, dev_id,
|
|
0, &retval);
|
|
if (err)
|
|
return err;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int aaeon_gpio_output_set_direction(struct gpio_chip *chip,
|
|
unsigned int offset, int value)
|
|
{
|
|
int err, retval;
|
|
u32 dev_id = 0x0;
|
|
|
|
dev_id |= offset;
|
|
err = asus_wmi_evaluate_method(SET_DIRECTION_METHOD_ID, dev_id,
|
|
0, &retval);
|
|
if (err)
|
|
return err;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int aaeon_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
|
{
|
|
int err, retval;
|
|
u32 dev_id = 0x0;
|
|
|
|
dev_id |= offset;
|
|
err = asus_wmi_evaluate_method(GET_LEVEL_METHOD_ID, dev_id, 0, &retval);
|
|
if (err)
|
|
return err;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void aaeon_gpio_set(struct gpio_chip *chip, unsigned int offset,
|
|
int value)
|
|
{
|
|
int retval;
|
|
u32 dev_id = offset;
|
|
|
|
if (value)
|
|
dev_id = BIT(16) | dev_id;
|
|
|
|
asus_wmi_evaluate_method(SET_LEVEL_METHOD_ID, dev_id, 0, &retval);
|
|
}
|
|
|
|
static int aaeon_gpio_get_number(void)
|
|
{
|
|
int err, retval;
|
|
|
|
err = asus_wmi_evaluate_method(GET_GPIO_NUMBER_ID,
|
|
GET_SIO_NUMBER_METHOD_ID,
|
|
0, &retval);
|
|
if (err)
|
|
return err;
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int __init aaeon_gpio_probe(struct platform_device *pdev)
|
|
{
|
|
int err, i;
|
|
int dio_number = 0;
|
|
struct aaeon_gpio_data *data;
|
|
struct aaeon_gpio_bank *bank;
|
|
|
|
/* Prevent other drivers adding this platfom device */
|
|
if (!wmi_has_guid(AAEON_WMI_MGMT_GUID)) {
|
|
pr_debug("AAEON Management GUID not found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
dio_number = aaeon_gpio_get_number();
|
|
if (dio_number < 0)
|
|
return -ENODEV;
|
|
|
|
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->nr_bank = ARRAY_SIZE(aaeon_gpio_bank);
|
|
data->bank = aaeon_gpio_bank;
|
|
platform_set_drvdata(pdev, data);
|
|
bank = &data->bank[0];
|
|
bank->chip.parent = &pdev->dev;
|
|
bank->chip.ngpio = dio_number;
|
|
bank->data = data;
|
|
err = devm_gpiochip_add_data(&pdev->dev, &bank->chip, bank);
|
|
if (err)
|
|
pr_debug("Failed to register gpiochip %d: %d\n", i, err);
|
|
|
|
return err;
|
|
}
|
|
|
|
static struct platform_driver aaeon_gpio_driver = {
|
|
.driver = {
|
|
.name = "gpio-aaeon",
|
|
},
|
|
};
|
|
|
|
module_platform_driver_probe(aaeon_gpio_driver, aaeon_gpio_probe);
|
|
|
|
MODULE_ALIAS("platform:gpio-aaeon");
|
|
MODULE_DESCRIPTION("AAEON GPIO Driver");
|
|
MODULE_AUTHOR("Edward Lin <edward1_lin@aaeon.com.tw>");
|
|
MODULE_LICENSE("GPL v2");
|