GPIO expander device moudle을 만들어보자
device를 만들기 위해 CY8C9520A device를 사용한다
NESTED THREAD GPIO irqchps driver를 사용하여 CY8C9520A driver를 제작한다
4_CY8C9520A_rpi3.c
/*
* Driver for Cypress cy8c9520a I/O Expander
* Based on Intel Corporation cy8c9540a driver.
*/
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio/driver.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#define DRV_NAME "cy8c9520a"
/* cy8c9520a settings */
#define NGPIO 20
#define DEVID_CY8C9520A 0x20
#define NPORTS 3
/* Register offset */
#define REG_INPUT_PORT0 0x00
#define REG_OUTPUT_PORT0 0x08
#define REG_INTR_STAT_PORT0 0x10
#define REG_PORT_SELECT 0x18
#define REG_SELECT_PWM 0x1a
#define REG_INTR_MASK 0x19
#define REG_PIN_DIR 0x1c
#define REG_DRIVE_PULLUP 0x1d
#define REG_DRIVE_PULLDOWN 0x1e
#define REG_DEVID_STAT 0x2e
/* definition of the global structure for the driver */
struct cy8c9520a {
struct i2c_client *client;
struct gpio_chip gpio_chip;
struct gpio_desc *gpio;
int irq;
struct mutex lock;
/* protect serialized access to the interrupt controller bus */
struct mutex irq_lock;
/* cached output registers */
u8 outreg_cache[NPORTS];
/* cached IRQ mask */
u8 irq_mask_cache[NPORTS];
/* IRQ mask to be applied */
u8 irq_mask[NPORTS];
};
/* Per-port GPIO offset */
static const u8 cy8c9520a_port_offs[] = {
0,
8,
16,
};
/* return the port of the gpio */
static inline u8 cypress_get_port(unsigned int gpio)
{
u8 i = 0;
for (i = 0; i < sizeof(cy8c9520a_port_offs) - 1; i ++) {
if (! (gpio / cy8c9520a_port_offs[i + 1]))
break;
}
return i;
}
/* get the gpio offset inside its respective port */
static inline u8 cypress_get_offs(unsigned gpio, u8 port)
{
return gpio - cy8c9520a_port_offs[port];
}
/*
* struct gpio_chip get callback function.
* It gets the input value of the GPIO line (0=low, 1=high)
* accessing to the REG_INPUT_PORT register
int (*get)(struct gpio_chip *chip,
unsigned offset);
*/
static int cy8c9520a_gpio_get(struct gpio_chip *chip,
unsigned int gpio)
{
int ret;
u8 port, in_reg;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
dev_info(chip->parent, "cy8c9520a_gpio_get function is called\n");
/* get the input port address address (in_reg) for the GPIO */
port = cypress_get_port(gpio);
in_reg = REG_INPUT_PORT0 + port;
dev_info(chip->parent, "the in_reg address is %u\n", in_reg);
mutex_lock(&cygpio->lock);
ret = i2c_smbus_read_byte_data(cygpio->client, in_reg);
if (ret < 0) {
dev_err(chip->parent, "can't read input port %u\n", in_reg);
}
dev_info(chip->parent,
"cy8c9520a_gpio_get function with %d value is returned\n",
ret);
mutex_unlock(&cygpio->lock);
/*
* check the status of the GPIO in its input port register
* and return it. If expression is not 0 returns 1
*/
return !!(ret & BIT(cypress_get_offs(gpio, port)));
}
/*
* struct gpio_chip set callback function.
* It sets the output value of the GPIO line in
* GPIO ACTIVE_HIGH mode (0=low, 1=high)
* writing to the REG_OUTPUT_PORT register
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
*/
static void cy8c9520a_gpio_set(struct gpio_chip *chip,
unsigned int gpio, int val)
{
int ret;
u8 port, out_reg;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
dev_info(chip->parent,
"cy8c9520a_gpio_set_value func with %d value is called\n",
val);
/* get the output port address address (out_reg) for the GPIO */
port = cypress_get_port(gpio);
out_reg = REG_OUTPUT_PORT0 + port;
mutex_lock(&cygpio->lock);
/*
* if val is 1, gpio output level is high
* if val is 0, gpio output level is low
* the output registers were previously cached in cy8c9520a_setup()
*/
if (val) {
cygpio->outreg_cache[port] |= BIT(cypress_get_offs(gpio, port));
} else {
cygpio->outreg_cache[port] &= ~BIT(cypress_get_offs(gpio, port));
}
ret = i2c_smbus_write_byte_data(cygpio->client, out_reg,
cygpio->outreg_cache[port]);
if (ret < 0) {
dev_err(chip->parent, "can't write output port %u\n", port);
}
mutex_unlock(&cygpio->lock);
}
/*
* struct gpio_chip direction_output callback function.
* It configures the GPIO as an output writing to
* the REG_PIN_DIR register of the selected port
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
*/
static int cy8c9520a_gpio_direction_output(struct gpio_chip *chip,
unsigned int gpio, int val)
{
int ret;
u8 pins, port;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
/* gets the port number of the gpio */
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_gpio_direction output is called\n");
mutex_lock(&cygpio->lock);
/* select the port where we want to config the GPIO as output */
ret = i2c_smbus_write_byte_data(cygpio->client, REG_PORT_SELECT, port);
if (ret < 0) {
dev_err(chip->parent, "can't select port %u\n", port);
goto err;
}
ret = i2c_smbus_read_byte_data(cygpio->client, REG_PIN_DIR);
if (ret < 0) {
dev_err(chip->parent, "can't read pin direction\n");
goto err;
}
/* simply transform int to u8 */
pins = (u8)ret & 0xff;
/* add the direction of the new pin. Set 1 if input and set 0 is output */
pins &= ~BIT(cypress_get_offs(gpio, port));
ret = i2c_smbus_write_byte_data(cygpio->client, REG_PIN_DIR, pins);
if (ret < 0) {
dev_err(chip->parent, "can't write pin direction\n");
}
err:
mutex_unlock(&cygpio->lock);
cy8c9520a_gpio_set(chip, gpio, val);
return ret;
}
/*
* struct gpio_chip direction_input callback function.
* It configures the GPIO as an input writing to
* the REG_PIN_DIR register of the selected port
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
*/
static int cy8c9520a_gpio_direction_input(struct gpio_chip *chip,
unsigned int gpio)
{
int ret;
u8 pins, port;
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
/* gets the port number of the gpio */
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_gpio_direction input is called\n");
mutex_lock(&cygpio->lock);
/* select the port where we want to config the GPIO as input */
ret = i2c_smbus_write_byte_data(cygpio->client, REG_PORT_SELECT, port);
if (ret < 0) {
dev_err(chip->parent, "can't select port %u\n", port);
goto err;
}
ret = i2c_smbus_read_byte_data(cygpio->client, REG_PIN_DIR);
if (ret < 0) {
dev_err(chip->parent, "can't read pin direction\n");
goto err;
}
/* simply transform int to u8 */
pins = (u8)ret & 0xff;
/*
* add the direction of the new pin.
* Set 1 if input (out == 0) and set 0 is ouput (out == 1)
*/
pins |= BIT(cypress_get_offs(gpio, port));
ret = i2c_smbus_write_byte_data(cygpio->client, REG_PIN_DIR, pins);
if (ret < 0) {
dev_err(chip->parent, "can't write pin direction\n");
goto err;
}
err:
mutex_unlock(&cygpio->lock);
return ret;
}
/* function to lock access to slow bus (i2c) chips */
static void cy8c9520a_irq_bus_lock(struct irq_data *d)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
dev_info(chip->parent, "cy8c9520a_irq_bus_lock is called\n");
mutex_lock(&cygpio->irq_lock);
}
/*
* function to sync and unlock slow bus (i2c) chips
* REG_INTR_MASK register is accessed via I2C
* write 0 to the interrupt mask register line to
* activate the interrupt on the GPIO
*/
static void cy8c9520a_irq_bus_sync_unlock(struct irq_data *d)
{
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
int ret, i;
unsigned int gpio;
u8 port;
dev_info(chip->parent, "cy8c9520a_irq_bus_sync_unlock is called\n");
/* unmask the interrupt of the selected GPIO */
gpio = d->hwirq;
port = cypress_get_port(gpio);
cygpio->irq_mask[port] &= ~BIT(cypress_get_offs(gpio, port));
/* irq_mask_cache stores the last value of irq_mask for each port */
for (i = 0; i < NPORTS; i++) {
/*
* check if some of the bits have changed from the last cached value
* irq_mask registers were initialized in cy8c9520a_irq_setup()
*/
if (cygpio->irq_mask_cache[i] ^ cygpio->irq_mask[i]) {
dev_info(chip->parent, "gpio %u is unmasked\n", gpio);
cygpio->irq_mask_cache[i] = cygpio->irq_mask[i];
ret = i2c_smbus_write_byte_data(cygpio->client,
REG_PORT_SELECT, i);
if (ret < 0) {
dev_err(chip->parent, "can't select port %u\n", port);
goto err;
}
/* enable the interrupt for the GPIO unmasked */
ret = i2c_smbus_write_byte_data(cygpio->client, REG_INTR_MASK,
cygpio->irq_mask[i]);
if (ret < 0) {
dev_err(chip->parent,
"can't write int mask on port %u\n", port);
goto err;
}
ret = i2c_smbus_read_byte_data(cygpio->client, REG_INTR_MASK);
dev_info(chip->parent, "the REG_INTR_MASK value is %d\n", ret);
}
}
err:
mutex_unlock(&cygpio->irq_lock);
}
/*
* mask (disable) the GPIO interrupt.
* In the initial setup all the int lines are masked
*/
static void cy8c9520a_irq_mask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_irq_mask is called\n");
cygpio->irq_mask[port] |= BIT(cypress_get_offs(gpio, port));
}
/*
* unmask (enable) the GPIO interrupt.
* In the initial setup all the int lines are masked
*/
static void cy8c9520a_irq_unmask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
dev_info(chip->parent, "cy8c9520a_irq_unmask is called\n");
cygpio->irq_mask[port] &= ~BIT(cypress_get_offs(gpio, port));
}
/* set the flow type (IRQ_TYPE_LEVEL/etc.) of the IRQ */
static int cy8c9520a_irq_set_type(struct irq_data *d, unsigned int type)
{
int ret = 0;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
dev_info(chip->parent, "cy8c9520a_irq_set_type is called\n");
if ((type != IRQ_TYPE_EDGE_BOTH) && (type != IRQ_TYPE_EDGE_FALLING)) {
dev_err(&cygpio->client->dev, "irq %d: unsupported type %d\n",
d->irq, type);
ret = -EINVAL;
goto err;
}
err:
return ret;
}
/* Iinitialization of the irq_chip structure with callback functions */
static struct irq_chip cy8c9520a_irq_chip = {
.name = "cy8c9520a-irq",
.irq_mask = cy8c9520a_irq_mask,
.irq_unmask = cy8c9520a_irq_unmask,
.irq_bus_lock = cy8c9520a_irq_bus_lock,
.irq_bus_sync_unlock = cy8c9520a_irq_bus_sync_unlock,
.irq_set_type = cy8c9520a_irq_set_type,
};
/*
* interrupt handler for the cy8c9520a. It is called when
* there is a rising or falling edge in the unmasked GPIO
*/
static irqreturn_t cy8c9520a_irq_handler(int irq, void *devid)
{
struct cy8c9520a *cygpio = devid;
u8 stat[NPORTS], pending;
unsigned port, gpio, gpio_irq;
int ret;
pr_info ("the interrupt ISR has been entered\n");
/*
* store in stat and clear (to enable ints)
* the three interrupt status registers by reading them
*/
ret = i2c_smbus_read_i2c_block_data(cygpio->client,
REG_INTR_STAT_PORT0,
NPORTS, stat);
if (ret < 0) {
memset(stat, 0, sizeof(stat));
}
ret = IRQ_NONE;
for (port = 0; port < NPORTS; port ++) {
mutex_lock(&cygpio->irq_lock);
/*
* In every port check the GPIOs that have their int unmasked
* and whose bits have been enabled in their REG_INTR_STAT_PORT
* register due to an interrupt in the GPIO, and store the new
* value in the pending register
*/
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
/* Launch the ISRs of all the gpios that requested an interrupt */
while (pending) {
ret = IRQ_HANDLED;
/* get the first gpio that has got an int */
gpio = __ffs(pending);
/* clears the gpio in the pending register */
pending &= ~BIT(gpio);
/* gets the int number associated to this gpio */
gpio_irq = cy8c9520a_port_offs[port] + gpio;
/* launch the ISR of the GPIO child driver */
handle_nested_irq(irq_find_mapping(cygpio->gpio_chip.irq.domain,
gpio_irq));
}
}
return ret;
}
/* Initial setup for the cy8c9520a */
static int cy8c9520a_setup(struct cy8c9520a *cygpio)
{
int ret, i;
struct i2c_client *client = cygpio->client;
/* Disable PWM, set all GPIOs as input. */
for (i = 0; i < NPORTS; i ++) {
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i);
if (ret < 0) {
dev_err(&client->dev, "can't select port %u\n", i);
goto end;
}
ret = i2c_smbus_write_byte_data(client, REG_SELECT_PWM, 0x00);
if (ret < 0) {
dev_err(&client->dev, "can't write to SELECT_PWM\n");
goto end;
}
ret = i2c_smbus_write_byte_data(client, REG_PIN_DIR, 0xff);
if (ret < 0) {
dev_err(&client->dev, "can't write to PIN_DIR\n");
goto end;
}
}
/* Cache the output registers (Output Port 0, Output Port 1, Output Port 2) */
ret = i2c_smbus_read_i2c_block_data(client, REG_OUTPUT_PORT0,
sizeof(cygpio->outreg_cache),
cygpio->outreg_cache);
if (ret < 0) {
dev_err(&client->dev, "can't cache output registers\n");
goto end;
}
dev_info(&client->dev, "the cy8c9520a_setup is done\n");
end:
return ret;
}
/* Interrupt setup for the cy8c9520a */
static int cy8c9520a_irq_setup(struct cy8c9520a *cygpio)
{
struct i2c_client *client = cygpio->client;
struct gpio_chip *chip = &cygpio->gpio_chip;
u8 dummy[NPORTS];
int ret, i;
mutex_init(&cygpio->irq_lock);
dev_info(&client->dev, "the cy8c9520a_irq_setup function is entered\n");
/*
* Clear interrupt state registers by reading the three registers
* Interrupt Status Port0, Interrupt Status Port1, Interrupt Status Port2,
* and store the values in a dummy array
* s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client, u8 command,
u8 length, u8 *values);
client: I2C 클라이언트를 나타내는 struct i2c_client 포인터입니다.
command: 읽고자 하는 레지스터의 명령(command) 값을 나타내는 8비트(unsigned char) 값입니다.
length: 읽어올 블록 데이터의 길이를 나타내는 8비트(unsigned char) 값입니다.
values: 읽어온 데이터를 저장할 버퍼로, length 크기의 메모리 공간을 할당하여 전달해야 합니다.
*
*/
ret = i2c_smbus_read_i2c_block_data(client, REG_INTR_STAT_PORT0,
NPORTS, dummy);
if (ret < 0) {
dev_err(&client->dev, "couldn't clear int status\n");
goto err;
}
dev_info(&client->dev, "the interrupt state registers are cleared\n");
/*
* Initialise Interrupt Mask Port Register (19h) for each port
* Disable the activation of the INT lines. Each 1 in this
* register masks (disables) the int from the corresponding GPIO
*/
memset(cygpio->irq_mask_cache, 0xff, sizeof(cygpio->irq_mask_cache));
memset(cygpio->irq_mask, 0xff, sizeof(cygpio->irq_mask));
/* Disable interrupts in all the gpio lines */
for (i = 0; i < NPORTS; i++) {
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i);
if (ret < 0) {
dev_err(&client->dev, "can't select port %u\n", i);
goto err;
}
ret = i2c_smbus_write_byte_data(client, REG_INTR_MASK,
cygpio->irq_mask[i]);
if (ret < 0) {
dev_err(&client->dev,
"can't write int mask on port %u\n", i);
goto err;
}
}
dev_info(&client->dev, "the interrupt mask port registers are set\n");
/* add a nested irqchip to the gpiochip
int gpiochip_irqchip_add_nested(struct gpio_chip *gpiochip,
struct irq_chip *irqchip,
int irq_base, irq_hw_number_t num,
irq_flow_handler_t handler,
void *handle)
gpiochip: GPIO 컨트롤러를 나타내는 struct gpio_chip 포인터입니다.
irqchip: IRQ 컨트롤러를 나타내는 struct irq_chip 포인터입니다.
irq_base: 중첩된 IRQ의 기준 IRQ 번호입니다.
num: IRQ 개수를 나타내는 irq_hw_number_t 값입니다.
handler: IRQ 처리를 담당하는 IRQ 플로우 핸들러 함수입니다.
handle: IRQ 플로우 핸들러에서 사용할 데이터 또는 핸들입니다.
*/
ret = gpiochip_irqchip_add_nested(chip,
&cy8c9520a_irq_chip,
0,
handle_simple_irq,
IRQ_TYPE_NONE);
if (ret) {
dev_err(&client->dev,
"could not connect irqchip to gpiochip\n");
return ret;
}
/*
* Request interrupt on a GPIO pin of the external processor
* this processor pin is connected to the INT pin of the cy8c9520a
*/
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
cy8c9520a_irq_handler,
IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
dev_name(&client->dev), cygpio);
if (ret) {
dev_err(&client->dev, "failed to request irq %d\n", cygpio->irq);
return ret;
}
/*
* set up a nested irq handler for a gpio_chip from a parent IRQ
* you can now request interrupts from GPIO child drivers nested
* to the cy8c9520a driver
* gpio controler에 nested irq 를 연결한다.
*/
gpiochip_set_nested_irqchip(chip,
&cy8c9520a_irq_chip,
cygpio->irq);
dev_info(&client->dev, "the interrupt setup is done\n");
return 0;
err:
mutex_destroy(&cygpio->irq_lock);
return ret;
}
/*
* Initialize the cy8c9520a gpio controller (struct gpio_chip)
* and register it to the kernel
*/
static int cy8c9520a_gpio_init(struct cy8c9520a *cygpio)
{
struct gpio_chip *gpiochip = &cygpio->gpio_chip;
int err;
gpiochip->label = cygpio->client->name;
gpiochip->base = -1;
gpiochip->ngpio = NGPIO;
gpiochip->parent = &cygpio->client->dev;
gpiochip->of_node = gpiochip->parent->of_node;
gpiochip->can_sleep = true;
gpiochip->direction_input = cy8c9520a_gpio_direction_input;
gpiochip->direction_output = cy8c9520a_gpio_direction_output;
gpiochip->get = cy8c9520a_gpio_get;
gpiochip->set = cy8c9520a_gpio_set;
gpiochip->owner = THIS_MODULE;
/* register a gpio_chip */
err = devm_gpiochip_add_data(gpiochip->parent, gpiochip, cygpio);
if (err)
return err;
return 0;
}
static int cy8c9520a_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct cy8c9520a *cygpio;
int ret;
unsigned int dev_id;
dev_info(&client->dev, "cy8c9520a_probe() function is called\n");
/*
i2c_check_functionality : i2c장치가 어떠한 기능을 제공하는지 확인
*/
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_I2C_BLOCK |
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBUS Byte/Block unsupported\n");
return -EIO;
}
/* allocate global private structure for a new device */
cygpio = devm_kzalloc(&client->dev, sizeof(*cygpio), GFP_KERNEL);
if (!cygpio) {
dev_err(&client->dev, "failed to alloc memory\n");
return -ENOMEM;
}
cygpio->client = client;
mutex_init(&cygpio->lock);
/* Whoami */
/* REG_DEVID_STAT 레지스터를 읽는다.*/
dev_id = i2c_smbus_read_byte_data(client, REG_DEVID_STAT);
if (dev_id < 0) {
dev_err(&client->dev, "can't read device ID\n");
ret = dev_id;
goto err;
}
dev_info(&client->dev, "dev_id=0x%x\n", dev_id & 0xff);
/* Initial setup for the cy8c9520a */\
ret = cy8c9520a_setup(cygpio);
if (ret < 0) {
goto err;
}
/* Initialize the cy8c9520a gpio controller
struct gpio_chip를 초기화 한다
*/
ret = cy8c9520a_gpio_init(cygpio);
if (ret) {
goto err;
}
/* Interrupt setup for the cy8c9520a */
ret = cy8c9520a_irq_setup(cygpio);
if (ret) {
goto err;
}
/* link the I2C device with the cygpio device
void i2c_set_clientdata(struct i2c_client *client, void *data);
client에 data를 연결
*/
i2c_set_clientdata(client, cygpio);
return 0;
err:
mutex_destroy(&cygpio->lock);
return ret;
}
static int cy8c9520a_remove(struct i2c_client *client)
{
dev_info(&client->dev, "cy8c9520a_remove() function is called\n");
return 0;
}
static const struct of_device_id my_of_ids[] = {
{ .compatible = "cy8c9520a"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static const struct i2c_device_id cy8c9520a_id[] = {
{DRV_NAME, 0},
{}
};
MODULE_DEVICE_TABLE(i2c, cy8c9520a_id);
static struct i2c_driver cy8c9520a_driver = {
.driver = {
.name = DRV_NAME,
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
},
.probe = cy8c9520a_probe,
.remove = cy8c9520a_remove,
.id_table = cy8c9520a_id,
};
module_i2c_driver(cy8c9520a_driver);
MODULE_LICENSE("GPL v2");
5_int_rpi3_gpio.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/gpio/consumer.h>
#include <linux/miscdevice.h>
#include <linux/of_device.h>
static char *INT_NAME = "P0_line0_INT";
/* interrupt handler */
static irqreturn_t P0_line0_isr(int irq, void *data)
{
struct device *dev = data;
dev_info(dev, "interrupt received. key: %s\n", INT_NAME);
return IRQ_HANDLED;
}
static struct miscdevice helloworld_miscdevice = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
};
static int my_probe(struct platform_device *pdev)
{
int ret_val, irq;
struct device *dev = &pdev->dev;
dev_info(dev, "my_probe() function is called.\n");
/* Get the Linux IRQ number */
irq = platform_get_irq(pdev, 0);
if (irq < 0){
dev_err(dev, "irq is not available\n");
return -EINVAL;
}
dev_info(dev, "IRQ_using_platform_get_irq: %d\n", irq);
/* Allocate the interrupt line */
ret_val = devm_request_threaded_irq(dev, irq, NULL, P0_line0_isr,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
INT_NAME, dev);
if (ret_val) {
dev_err(dev, "Failed to request interrupt %d, error %d\n", irq, ret_val);
return ret_val;
}
ret_val = misc_register(&helloworld_miscdevice);
if (ret_val != 0)
{
dev_err(dev, "could not register the misc device mydev\n");
return ret_val;
}
dev_info(dev, "mydev: got minor %i\n",helloworld_miscdevice.minor);
dev_info(dev, "my_probe() function is exited.\n");
return 0;
}
static int my_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "my_remove() function is called.\n");
misc_deregister(&helloworld_miscdevice);
dev_info(&pdev->dev, "my_remove() function is exited.\n");
return 0;
}
static const struct of_device_id my_of_ids[] = {
{ .compatible = "arrow,int_gpio_expand"},
{},
};
MODULE_DEVICE_TABLE(of, my_of_ids);
static struct platform_driver my_platform_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "int_gpio_expand",
.of_match_table = my_of_ids,
.owner = THIS_MODULE,
}
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
6_ex_gpio_init.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <string.h>
#include <linux/gpio.h>
#include <sys/ioctl.h>
#define DEV_GPIO "/dev/gpiochip3"
#define POLL_TIMEOUT -1 /* No timeout */
int main(int argc, char *argv[])
{
int fd, fd_in;
int ret;
int flags;
struct gpioevent_request req;
struct gpioevent_data evdata;
struct pollfd fdset;
/* open gpio */
fd = open(DEV_GPIO, O_RDWR);
if (fd < 0) {
printf("ERROR: open %s ret=%d\n", DEV_GPIO, fd);
return -1;
}
/* Request GPIO P0 first line interrupt */
req.lineoffset = 0;
req.handleflags = GPIOHANDLE_REQUEST_INPUT;
req.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
strncpy(req.consumer_label, "gpio_irq", sizeof(req.consumer_label) - 1);
/* requrest line event handle */
ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &req);
if (ret) {
printf("ERROR: ioctl get line event ret=%d\n", ret);
return -1;
}
/* set event fd nonbloack read */
fd_in = req.fd;
flags = fcntl(fd_in, F_GETFL);
flags |= O_NONBLOCK;
ret = fcntl(fd_in, F_SETFL, flags);
if (ret) {
printf("ERROR: fcntl set nonblock read\n");
}
for (;;) {
fdset.fd = fd_in;
fdset.events = POLLIN;
fdset.revents = 0;
/* poll gpio line event */
ret = poll(&fdset, 1, POLL_TIMEOUT);
if (ret <= 0)
continue;
if (fdset.revents & POLLIN) {
printf("irq received.\n");
/* read event data */
ret = read(fd_in, &evdata, sizeof(evdata));
if (ret == sizeof(evdata))
printf("id: %d, timestamp: %lld\n", evdata.id, evdata.timestamp);
}
}
/* close gpio */
close(fd);
return 0;
}
device tree
&soc {
...
int_gpio {
compatible = "arrow,int_gpio_expand";
pinctrl-names = "default";
interrupt-parent = <&cy8c9520a>;
interrupts = <0 IRQ_TYPE_EDGE_BOTH>;
};
&i2c1 {
...
cy8c9520a: cy8c9520a@20{
compatible = "cy8c9520a";
reg = <0x20>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};
CY8C9520A device datasheete
• Connect Raspberry Pi SCL to CY8C9520A SCL (Pin 12 of Mikrobus).
• Connect Raspberry Pi SDA to CY8C9520A SDA (Pin 11 of Mikrobus).
• Connect Raspberry Pi GPIO23 to CY8C9520A INT (Pin 15 of Mikrobus).
• Connect Raspberry Pi 3.3V to CY8C9520A 3.3V (Pin 7 of Mikrobus).
• Connect Raspberry Pi GND to CY8C9520A GND (Pin 8 of Mikrobus).
CY8C9520A 드라이버는 gpio_chip 구조를 커널에 등록하고 irq_chip 구조를 IRQ 하위 시스템에 등록합니다.
GPIO irqchip은 NESTED THREADED GPIO IRQCHIPS 범주에 속할 것입니다. 이는 I2C 또는 SPI와 같이 슬리핑 버스의 반대편에 상주하는 오프칩 GPIO 확장기입니다. GPIOlib 프레임워크는 GPIO를 제어하고 해당 인터럽트를 처리하는 커널 및 사용자 공간 API를 제공합니다.
devm_gpiochip_add_data() 함수를 사용하여 gpio controller의 gpio line을 컨트롤하며, gpiochip을 커널에 등록한다
gpiochip_irqchip_add_nested() 함수는 nested irqchp을 gpiochp에 추가를한다
위 함수는 handle_simple_irq flow handler를 따른다
(Linux generic IRQ handling — The Linux Kernel documentation)
이 interrupt handler는 handle_nested_irq()함수로 인해 새로운 thread를 만든다 그리고 내부적으로 CY8C9520A driver의 interrupt handler를 호출한다
gpiochip_set_nested_irqchip()
* 상위 IRQ에서 gpio_chip에 대해 중첩된 irq 핸들러를 설정하면 이제 cy8c9520a 드라이버에 중첩된 GPIO 하위 드라이버에서 인터럽트를 요청할 수 있습니다.
/**
* devm_gpiochip_add_data() - Resource manager gpiochip_add_data()
* @dev: pointer to the device that gpio_chip belongs to.
* @chip: the chip to register, with chip->base initialized
* @data: driver-private data associated with this chip
*
* Context: potentially before irqs will work
*
* The gpio chip automatically be released when the device is unbound.
*
* Returns:
* A negative errno if the chip can't be registered, such as because the
* chip->base is invalid or already associated with a different chip.
* Otherwise it returns zero as a success code.
*/
int devm_gpiochip_add_data(struct device *dev, struct gpio_chip *chip,
void *data)
gpiochip을 등록한다
insmod 4_CY8C9520A_rpi3.ko
[ 108.453929] 4_CY8C9520A_rpi3: loading out-of-tree module taints kernel.
[ 108.455233] cy8c9520a 1-0020: cy8c9520a_probe() function is called
[ 108.457407] cy8c9520a 1-0020: dev_id=0x20
[ 108.476399] cy8c9520a 1-0020: the cy8c9520a_setup is done
[ 108.476942] cy8c9520a 1-0020: the cy8c9520a_irq_setup function is entered
[ 108.480002] cy8c9520a 1-0020: the interrupt state registers are cleared
[ 108.490692] cy8c9520a 1-0020: the interrupt mask port registers are set
[ 108.491087] cy8c9520a 1-0020: the interrupt setup is done
함수 호출 순서
cy8c9520a_probe() -> cy8c9520a_setup() -> cy8c9520a_irq_setup()
expander gpio p0에서 0번과 1번을 연결한다
root@raspberrypi:/home/board# gpioinfo gpiochip3
gpiochip3 - 20 lines:
line 0: unnamed unused input active-high
line 1: unnamed unused input active-high
line 2: unnamed unused input active-high
line 3: unnamed unused input active-high
line 4: unnamed unused input active-high
line 5: unnamed unused input active-high
line 6: unnamed unused input active-high
line 7: unnamed unused input active-high
line 8: unnamed unused input active-high
line 9: unnamed unused input active-high
line 10: unnamed unused input active-high
line 11: unnamed unused input active-high
line 12: unnamed unused input active-high
line 13: unnamed unused input active-high
line 14: unnamed unused input active-high
line 15: unnamed unused input active-high
line 16: unnamed unused input active-high
line 17: unnamed unused input active-high
line 18: unnamed unused input active-high
line 19: unnamed unused input active-high
root@raspberrypi:/home/board# gpioset gpiochip3 1=1
root@raspberrypi:/home/board# gpioinfo gpiochip3
gpiochip3 - 20 lines:
line 0: unnamed unused input active-high
line 1: unnamed unused output active-high
line 2: unnamed unused input active-high
line 3: unnamed unused input active-high
line 4: unnamed unused input active-high
line 5: unnamed unused input active-high
line 6: unnamed unused input active-high
line 7: unnamed unused input active-high
line 8: unnamed unused input active-high
line 9: unnamed unused input active-high
line 10: unnamed unused input active-high
line 11: unnamed unused input active-high
line 12: unnamed unused input active-high
line 13: unnamed unused input active-high
line 14: unnamed unused input active-high
line 15: unnamed unused input active-high
line 16: unnamed unused input active-high
line 17: unnamed unused input active-high
line 18: unnamed unused input active-high
line 19: unnamed unused input active-high
root@raspberrypi:/home/board# dmesg
[10521.477998] cy8c9520a 1-0020: cy8c9520a_gpio_direction output is called
[10521.483237] cy8c9520a 1-0020: cy8c9520a_gpio_set_value func with 1 value is called
cy8c9520a_gpio_direction_output() 함수 호출
externel gpio 의 1번 핀은 high를 output 한다.
root@raspberrypi:/home/board# gpioset gpiochip3 1=0
root@raspberrypi:/home/board# dmesg
[10727.404515] cy8c9520a 1-0020: cy8c9520a_gpio_direction output is called
[10727.409840] cy8c9520a 1-0020: cy8c9520a_gpio_set_value func with 0 value is called
externel gpio 의 1번 핀은 low를 output 한다.
root@raspberrypi:/home/board# gpioget gpiochip3 0
0
root@raspberrypi:/home/board# dmesg
[ 2369.362372] cy8c9520a 1-0020: cy8c9520a_gpio_direction input is called
[ 2369.367682] cy8c9520a 1-0020: cy8c9520a_gpio_get function is called
[ 2369.367697] cy8c9520a 1-0020: the in_reg address is 0
[ 2369.369778] cy8c9520a 1-0020: cy8c9520a_gpio_get function with 0 value is returned
expander gpio p0에서 0번과 1번을 연결해제 한다.
insmod 5_int_rpi3_gpio.ko
[12213.731985] int_gpio_expand soc:int_gpio: my_probe() function is called.
[12213.732158] cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
[12213.732176] cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
[12213.732190] cy8c9520a 1-0020: gpio 0 is unmasked
[12213.738260] cy8c9520a 1-0020: the REG_INTR_MASK value is 254
[12213.738295] int_gpio_expand soc:int_gpio: IRQ_using_platform_get_irq: 167
[12213.738317] cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
[12213.738334] cy8c9520a 1-0020: cy8c9520a_irq_set_type is called
[12213.738347] cy8c9520a 1-0020: cy8c9520a_irq_unmask is called
[12213.738359] cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
[12213.747817] int_gpio_expand soc:int_gpio: mydev: got minor 60
[12213.747836] int_gpio_expand soc:int_gpio: my_probe() function is exited.
root@raspberrypi:/home/board# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
...
166: 0 0 0 0 pinctrl-bcm2835 23 Level 1-0020
167: 0 0 0 0 cy8c9520a-irq 0 Edge P0_line0_INT <------------- 생성
...
expander gpio p0에서 0번을 GND 에 연결 후 해제 한다 연결 할떄 한번, 해제할때 한번 이벤트가 발생
[ 96.995272] the interrupt ISR has been entered
[ 96.998385] int_gpio_expand soc:int_gpio: interrupt received. key: P0_line0_INT
[ 99.758354] the interrupt ISR has been entered
[ 99.761439] int_gpio_expand soc:int_gpio: interrupt received. key: P0_line0_INT
4번 module cy8c9520a_irq_handler() -> 5번 module P0_line0_isr
gpiomon --falling-edge gpiochip3 0
[ 452.669444] cy8c9520a 1-0020: cy8c9520a_gpio_direction input is called
[ 452.674848] cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
[ 452.674869] cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
[ 452.674883] cy8c9520a 1-0020: gpio 0 is unmasked
[ 452.680139] cy8c9520a 1-0020: the REG_INTR_MASK value is 254
[ 452.680171] cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
[ 452.680187] cy8c9520a 1-0020: cy8c9520a_irq_set_type is called
[ 452.680200] cy8c9520a 1-0020: cy8c9520a_irq_unmask is called
[ 452.680212] cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
[ 458.379963] cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
[ 458.379984] cy8c9520a 1-0020: cy8c9520a_irq_mask is called
[ 458.379998] cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
[ 458.380026] cy8c9520a 1-0020: cy8c9520a_irq_bus_lock is called
[ 458.380044] cy8c9520a 1-0020: cy8c9520a_irq_bus_sync_unlock is called
insmod 4_CY8C9520A_rpi3.ko
gpiomon --falling-edge gpiochip3 0
expander gpio p0에서 0번과 GND을 연결 한다
event: FALLING EDGE offset: 0 timestamp: [1687186353.784017924]
[ 57.412814] the interrupt ISR has been entered
expander gpio p0에서 0번과 GND을 연결해제 한다
event: FALLING EDGE offset: 0 timestamp: [1687186361.468375525]
[ 65.097153] the interrupt ISR has been entered
insmod 4_CY8C9520A_rpi3.ko
./6_ex_gpio_init
expander gpio p0에서 0번과 GND을 연결 한다
[ 283.599434] the interrupt ISR has been entered
[ 283.602510] cy8c9520a 1-0020: cy8c9520a_gpio_get function is called
[ 283.602526] cy8c9520a 1-0020: the in_reg address is 0
[ 283.604622] cy8c9520a 1-0020: cy8c9520a_gpio_get function with 254 value is returned
irq received.
id: 2, timestamp: 1687186596213937435
expander gpio p0에서 0번과 GND을 연결해제 한다
[ 285.764959] the interrupt ISR has been entered
[ 285.768083] cy8c9520a 1-0020: cy8c9520a_gpio_get function is called
[ 285.768101] cy8c9520a 1-0020: the in_reg address is 0
[ 285.770181] cy8c9520a 1-0020: cy8c9520a_gpio_get function with 255 value is returned
irq received.
id: 1, timestamp: 1687186597931893291
---
struct
/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @parent_device: pointer to parent device for irqchip
* @name: name for /proc/interrupts
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
* @irq_set_affinity: Set the CPU affinity on SMP machines. If the force
* argument is true, it tells the driver to
* unconditionally apply the affinity setting. Sanity
* checks against the supplied affinity mask are not
* required. This is used for CPU hotplug where the
* target CPU is not yet set in the cpu_online_mask.
* @irq_retrigger: resend an IRQ to the CPU
* @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @irq_set_wake: enable/disable power-management wake-on of an IRQ
* @irq_bus_lock: function to lock access to slow bus (i2c) chips
* @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
* @irq_cpu_online: configure an interrupt source for a secondary CPU
* @irq_cpu_offline: un-configure an interrupt source for a secondary CPU
* @irq_suspend: function called from core code on suspend once per
* chip, when one or more interrupts are installed
* @irq_resume: function called from core code on resume once per chip,
* when one ore more interrupts are installed
* @irq_pm_shutdown: function called from core code on shutdown once per chip
* @irq_calc_mask: Optional function to set irq_data.mask for special cases
* @irq_print_chip: optional to print special chip info in show_interrupts
* @irq_request_resources: optional to request resources before calling
* any other callback related to this irq
* @irq_release_resources: optional to release resources acquired with
* irq_request_resources
* @irq_compose_msi_msg: optional to compose message content for MSI
* @irq_write_msi_msg: optional to write message content for MSI
* @irq_get_irqchip_state: return the internal state of an interrupt
* @irq_set_irqchip_state: set the internal state of a interrupt
* @irq_set_vcpu_affinity: optional to target a vCPU in a virtual machine
* @ipi_send_single: send a single IPI to destination cpus
* @ipi_send_mask: send an IPI to destination cpus in cpumask
* @irq_nmi_setup: function called from core code before enabling an NMI
* @irq_nmi_teardown: function called from core code after disabling an NMI
* @flags: chip specific flags
*/
struct irq_chip {
struct device *parent_device;
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);
void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);
void (*irq_calc_mask)(struct irq_data *data);
void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
int (*irq_request_resources)(struct irq_data *data);
void (*irq_release_resources)(struct irq_data *data);
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
void (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);
int (*irq_nmi_setup)(struct irq_data *data);
void (*irq_nmi_teardown)(struct irq_data *data);
unsigned long flags;
};
/**
* struct gpio_chip - abstract a GPIO controller
* @label: a functional name for the GPIO device, such as a part
* number or the name of the SoC IP-block implementing it.
* @gpiodev: the internal state holder, opaque struct
* @parent: optional parent device providing the GPIOs
* @owner: helps prevent removal of modules exporting active GPIOs
* @request: optional hook for chip-specific activation, such as
* enabling module power and clock; may sleep
* @free: optional hook for chip-specific deactivation, such as
* disabling module power and clock; may sleep
* @get_direction: returns direction for signal "offset", 0=out, 1=in,
* (same as GPIOF_DIR_XXX), or negative error.
* It is recommended to always implement this function, even on
* input-only or output-only gpio chips.
* @direction_input: configures signal "offset" as input, or returns error
* This can be omitted on input-only or output-only gpio chips.
* @direction_output: configures signal "offset" as output, or returns error
* This can be omitted on input-only or output-only gpio chips.
* @get: returns value for signal "offset", 0=low, 1=high, or negative error
* @get_multiple: reads values for multiple signals defined by "mask" and
* stores them in "bits", returns 0 on success or negative error
* @set: assigns output value for signal "offset"
* @set_multiple: assigns output values for multiple signals defined by "mask"
* @set_config: optional hook for all kinds of settings. Uses the same
* packed config format as generic pinconf.
* @to_irq: optional hook supporting non-static gpio_to_irq() mappings;
* implementation may not sleep
* @dbg_show: optional routine to show contents in debugfs; default code
* will be used when this is omitted, but custom code can show extra
* state (such as pullup/pulldown configuration).
* @init_valid_mask: optional routine to initialize @valid_mask, to be used if
* not all GPIOs are valid.
* @base: identifies the first GPIO number handled by this chip;
* or, if negative during registration, requests dynamic ID allocation.
* DEPRECATION: providing anything non-negative and nailing the base
* offset of GPIO chips is deprecated. Please pass -1 as base to
* let gpiolib select the chip base in all possible cases. We want to
* get rid of the static GPIO number space in the long run.
* @ngpio: the number of GPIOs handled by this controller; the last GPIO
* handled is (base + ngpio - 1).
* @names: if set, must be an array of strings to use as alternative
* names for the GPIOs in this chip. Any entry in the array
* may be NULL if there is no alias for the GPIO, however the
* array must be @ngpio entries long. A name can include a single printk
* format specifier for an unsigned int. It is substituted by the actual
* number of the gpio.
* @can_sleep: flag must be set iff get()/set() methods sleep, as they
* must while accessing GPIO expander chips over I2C or SPI. This
* implies that if the chip supports IRQs, these IRQs need to be threaded
* as the chip access may sleep when e.g. reading out the IRQ status
* registers.
* @read_reg: reader function for generic GPIO
* @write_reg: writer function for generic GPIO
* @be_bits: if the generic GPIO has big endian bit order (bit 31 is representing
* line 0, bit 30 is line 1 ... bit 0 is line 31) this is set to true by the
* generic GPIO core. It is for internal housekeeping only.
* @reg_dat: data (in) register for generic GPIO
* @reg_set: output set register (out=high) for generic GPIO
* @reg_clr: output clear register (out=low) for generic GPIO
* @reg_dir_out: direction out setting register for generic GPIO
* @reg_dir_in: direction in setting register for generic GPIO
* @bgpio_dir_unreadable: indicates that the direction register(s) cannot
* be read and we need to rely on out internal state tracking.
* @bgpio_bits: number of register bits used for a generic GPIO i.e.
* <register width> * 8
* @bgpio_lock: used to lock chip->bgpio_data. Also, this is needed to keep
* shadowed and real data registers writes together.
* @bgpio_data: shadowed data register for generic GPIO to clear/set bits
* safely.
* @bgpio_dir: shadowed direction register for generic GPIO to clear/set
* direction safely. A "1" in this word means the line is set as
* output.
*
* A gpio_chip can help platforms abstract various sources of GPIOs so
* they can all be accessed through a common programing interface.
* Example sources would be SOC controllers, FPGAs, multifunction
* chips, dedicated GPIO expanders, and so on.
*
* Each chip controls a number of signals, identified in method calls
* by "offset" values in the range 0..(@ngpio - 1). When those signals
* are referenced through calls like gpio_get_value(gpio), the offset
* is calculated by subtracting @base from the gpio number.
*/
struct gpio_chip {
const char *label;
struct gpio_device *gpiodev;
struct device *parent;
struct module *owner;
int (*request)(struct gpio_chip *chip,
unsigned offset);
void (*free)(struct gpio_chip *chip,
unsigned offset);
int (*get_direction)(struct gpio_chip *chip,
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);
int (*get)(struct gpio_chip *chip,
unsigned offset);
int (*get_multiple)(struct gpio_chip *chip,
unsigned long *mask,
unsigned long *bits);
void (*set)(struct gpio_chip *chip,
unsigned offset, int value);
void (*set_multiple)(struct gpio_chip *chip,
unsigned long *mask,
unsigned long *bits);
int (*set_config)(struct gpio_chip *chip,
unsigned offset,
unsigned long config);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int (*init_valid_mask)(struct gpio_chip *chip,
unsigned long *valid_mask,
unsigned int ngpios);
int base;
u16 ngpio;
const char *const *names;
bool can_sleep;
#if IS_ENABLED(CONFIG_GPIO_GENERIC)
unsigned long (*read_reg)(void __iomem *reg);
void (*write_reg)(void __iomem *reg, unsigned long data);
bool be_bits;
void __iomem *reg_dat;
void __iomem *reg_set;
void __iomem *reg_clr;
void __iomem *reg_dir_out;
void __iomem *reg_dir_in;
bool bgpio_dir_unreadable;
int bgpio_bits;
spinlock_t bgpio_lock;
unsigned long bgpio_data;
unsigned long bgpio_dir;
#endif /* CONFIG_GPIO_GENERIC */
#ifdef CONFIG_GPIOLIB_IRQCHIP
/*
* With CONFIG_GPIOLIB_IRQCHIP we get an irqchip inside the gpiolib
* to handle IRQs for most practical cases.
*/
/**
* @irq:
*
* Integrates interrupt chip functionality with the GPIO chip. Can be
* used to handle IRQs for most practical cases.
*/
struct gpio_irq_chip irq;
#endif /* CONFIG_GPIOLIB_IRQCHIP */
/**
* @valid_mask:
*
* If not %NULL holds bitmask of GPIOs which are valid to be used
* from the chip.
*/
unsigned long *valid_mask;
#if defined(CONFIG_OF_GPIO)
/*
* If CONFIG_OF is enabled, then all GPIO controllers described in the
* device tree automatically may have an OF translation
*/
/**
* @of_node:
*
* Pointer to a device tree node representing this GPIO controller.
*/
struct device_node *of_node;
/**
* @of_gpio_n_cells:
*
* Number of cells used to form the GPIO specifier.
*/
unsigned int of_gpio_n_cells;
/**
* @of_xlate:
*
* Callback to translate a device tree GPIO specifier into a chip-
* relative GPIO number and flags.
*/
int (*of_xlate)(struct gpio_chip *gc,
const struct of_phandle_args *gpiospec, u32 *flags);
#endif /* CONFIG_OF_GPIO */
};
--
function
/**
* gpiochip_set_nested_irqchip() - connects a nested irqchip to a gpiochip
* @gpiochip: the gpiochip to set the irqchip nested handler to
* @irqchip: the irqchip to nest to the gpiochip
* @parent_irq: the irq number corresponding to the parent IRQ for this
* nested irqchip
*/
void gpiochip_set_nested_irqchip(struct gpio_chip *gpiochip,
struct irq_chip *irqchip,
unsigned int parent_irq)
'device driver' 카테고리의 다른 글
Waitqueue (0) | 2023.12.13 |
---|---|
gpio contorl 전반적인 내용 (0) | 2023.06.15 |
kernel linked list api 사용법 (0) | 2023.06.01 |
Waiting queues (0) | 2023.06.01 |
charter device driver - 8. interrupt platform driver 만들기 (0) | 2023.05.30 |
댓글