Commit e850ced1 authored by Viraaj Somayajula's avatar Viraaj Somayajula Committed by Carles Cufi
Browse files

gpio: gpio_sx1509b: add support for interrupt functionality



enable the interrupt functionality for sx1509b gpio expander,
when the CONFIG_GPIO_SX1509B_INTERRUPT config is enabled.
The gpio pin used for interrupt should be configured in the
device tree sx1509b node before enabling the interrupt
configuration.

Signed-off-by: default avatarViraaj Somayajula <sviraaj@zedblox.com>
Signed-off-by: default avatarPeter Bigot <peter.bigot@nordicsemi.no>
parent ae427177
Loading
Loading
Loading
Loading
+20 −1
Original line number Diff line number Diff line
@@ -13,9 +13,28 @@ menuconfig GPIO_SX1509B
	help
	  Enable driver for SX1509B I2C GPIO chip.

if GPIO_SX1509B

config GPIO_SX1509B_INIT_PRIORITY
	int "Init priority"
	depends on GPIO_SX1509B
	default 70
	help
	  Device driver initialization priority.

config GPIO_SX1509B_INTERRUPT
	bool "Interrupt enable"
	default n
	help
	  Enable support for interrupts on GPIO pins.

config GPIO_SX1509B_DEBOUNCE_TIME
	int "Debounce time interval"
	range 0 7
	default 0
	help
	  Debounce time interval when debounce enabled.

	  A value V produces a multiplier of 0.5 ms * 2^V, which is
	  then scaled by 2 MHz / fOSC.  See the datasheet for details.

endif # GPIO_SX1509B
+292 −22
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
 * Copyright (c) 2018 Aapo Vienamo
 * Copyright (c) 2018 Peter Bigot Consulting, LLC
 * Copyright (c) 2019 Nordic Semiconductor ASA
 * Copyright (c) 2020 ZedBlox Ltd.
 *
 * SPDX-License-Identifier: Apache-2.0
 */
@@ -43,7 +44,17 @@ struct sx1509b_pin_state {
	u16_t polarity;         /* 0x0C */
	u16_t dir;              /* 0x0E */
	u16_t data;             /* 0x10 */
};
} __packed;

struct sx1509b_irq_state {
	u16_t interrupt_mask;   /* 0x12 */
	u32_t interrupt_sense;  /* 0x14, 0x16 */
} __packed;

struct sx1509b_debounce_state {
	u8_t debounce_config;	/* 0x22 */
	u16_t debounce_enable;  /* 0x23 */
} __packed;

/** Runtime driver data */
struct sx1509b_drv_data {
@@ -51,7 +62,21 @@ struct sx1509b_drv_data {
	struct gpio_driver_data common;
	struct device *i2c_master;
	struct sx1509b_pin_state pin_state;
	struct sx1509b_debounce_state debounce_state;
	struct k_sem lock;

#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
	struct device *gpio_int;
	struct gpio_callback gpio_cb;
	struct k_work work;
	struct sx1509b_irq_state irq_state;
	struct device *dev;
	/* user ISR cb */
	sys_slist_t cb;
	/* Enabled INT pins generating a cb */
	u16_t cb_pins;
#endif /* CONFIG_GPIO_SX1509B_INTERRUPT */

};

/** Configuration data */
@@ -59,6 +84,11 @@ struct sx1509b_config {
	/* gpio_driver_config needs to be first */
	struct gpio_driver_config common;
	const char *i2c_master_dev_name;
#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
	const char *gpio_int_dev_name;
	gpio_pin_t gpio_pin;
	gpio_dt_flags_t gpio_flags;
#endif /* CONFIG_GPIO_SX1509B_INTERRUPT */
	u16_t i2c_slave_addr;
};

@@ -90,7 +120,21 @@ enum {
	SX1509B_REG_OPEN_DRAIN          = 0x0a,
	SX1509B_REG_DIR                 = 0x0e,
	SX1509B_REG_DATA                = 0x10,
	SX1509B_REG_LED_DRIVER_ENABLE   = 0x20,
	SX1509B_REG_INTERRUPT_MASK      = 0x12,
	SX1509B_REG_INTERRUPT_SENSE     = 0x14,
	SX1509B_REG_INTERRUPT_SENSE_B   = 0x14,
	SX1509B_REG_INTERRUPT_SENSE_A   = 0x16,
	SX1509B_REG_INTERRUPT_SOURCE    = 0x18,
	SX1509B_REG_DEBOUNCE_CONFIG     = 0x22,
	SX1509B_REG_DEBOUNCE_ENABLE     = 0x23,
};

/* Edge sensitivity types */
enum {
	SX1509B_EDGE_NONE     = 0x00,
	SX1509B_EDGE_RISING   = 0x01,
	SX1509B_EDGE_FALLING  = 0x02,
	SX1509B_EDGE_BOTH     = 0x03,
};

/**
@@ -112,6 +156,81 @@ static inline int i2c_reg_write_word_be(struct device *dev, u16_t dev_addr,
	return i2c_write(dev, tx_buf, 3, dev_addr);
}

/**
 * @brief Write a big-endian byte to an internal address of an I2C slave.
 *
 * @param dev Pointer to the device structure for the driver instance.
 * @param dev_addr Address of the I2C device for writing.
 * @param reg_addr Address of the internal register being written.
 * @param value Value to be written to internal register.
 *
 * @retval 0 If successful.
 * @retval -EIO General input / output error.
 */
static inline int i2c_reg_write_byte_be(struct device *dev, u16_t dev_addr,
					u8_t reg_addr, u8_t value)
{
	u8_t tx_buf[3] = { reg_addr, value };

	return i2c_write(dev, tx_buf, 2, dev_addr);
}

#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
static int sx1509b_handle_interrupt(void *arg)
{
	struct device *dev = (struct device *) arg;
	const struct sx1509b_config *cfg = dev->config->config_info;
	struct sx1509b_drv_data *drv_data = dev->driver_data;
	int ret = 0;
	u16_t int_source;
	u8_t cmd = SX1509B_REG_INTERRUPT_SOURCE;

	k_sem_take(&drv_data->lock, K_FOREVER);

	ret = i2c_write_read(drv_data->i2c_master, cfg->i2c_slave_addr,
			     &cmd, sizeof(cmd),
			     (u8_t *)&int_source, sizeof(int_source));
	if (ret != 0) {
		goto out;
	}

	int_source = sys_be16_to_cpu(int_source);

	/* reset interrupts before invoking callbacks */
	ret = i2c_reg_write_word_be(drv_data->i2c_master, cfg->i2c_slave_addr,
				    SX1509B_REG_INTERRUPT_SOURCE, int_source);

out:
	k_sem_give(&drv_data->lock);

	if ((ret == 0)
	    && ((int_source & drv_data->cb_pins) != 0)) {
		gpio_fire_callbacks(&drv_data->cb, dev, int_source);
	}

	return ret;
}

static void sx1509b_work_handler(struct k_work *work)
{
	struct sx1509b_drv_data *drv_data =
		CONTAINER_OF(work, struct sx1509b_drv_data, work);

	sx1509b_handle_interrupt(drv_data->dev);
}

static void sx1509_int_cb(struct device *dev, struct gpio_callback *gpio_cb,
			   u32_t pins)
{
	struct sx1509b_drv_data *drv_data = CONTAINER_OF(gpio_cb,
		struct sx1509b_drv_data, gpio_cb);

	ARG_UNUSED(pins);

	k_work_submit(&drv_data->work);
}
#endif

static int sx1509b_config(struct device *dev,
			  gpio_pin_t pin,
			  gpio_flags_t flags)
@@ -119,11 +238,11 @@ static int sx1509b_config(struct device *dev,
	const struct sx1509b_config *cfg = dev->config->config_info;
	struct sx1509b_drv_data *drv_data = dev->driver_data;
	struct sx1509b_pin_state *pins = &drv_data->pin_state;

	struct {
		u8_t reg;
		struct sx1509b_pin_state pins;
	} __packed outbuf;
	} __packed pin_buf;
	struct sx1509b_debounce_state *debounce = &drv_data->debounce_state;
	int rc = 0;
	bool data_first = false;

@@ -190,16 +309,22 @@ static int sx1509b_config(struct device *dev,
		pins->dir |= BIT(pin);
	}

	outbuf.reg = SX1509B_REG_INPUT_DISABLE;
	outbuf.pins.input_disable = sys_cpu_to_be16(pins->input_disable);
	outbuf.pins.long_slew = sys_cpu_to_be16(pins->long_slew);
	outbuf.pins.low_drive = sys_cpu_to_be16(pins->low_drive);
	outbuf.pins.pull_up = sys_cpu_to_be16(pins->pull_up);
	outbuf.pins.pull_down = sys_cpu_to_be16(pins->pull_down);
	outbuf.pins.open_drain = sys_cpu_to_be16(pins->open_drain);
	outbuf.pins.polarity = sys_cpu_to_be16(pins->polarity);
	outbuf.pins.dir = sys_cpu_to_be16(pins->dir);
	outbuf.pins.data = sys_cpu_to_be16(pins->data);
	if ((flags & GPIO_INT_DEBOUNCE) != 0) {
		debounce->debounce_enable |= BIT(pin);
	} else {
		debounce->debounce_enable &= ~BIT(pin);
	}

	pin_buf.reg = SX1509B_REG_INPUT_DISABLE;
	pin_buf.pins.input_disable = sys_cpu_to_be16(pins->input_disable);
	pin_buf.pins.long_slew = sys_cpu_to_be16(pins->long_slew);
	pin_buf.pins.low_drive = sys_cpu_to_be16(pins->low_drive);
	pin_buf.pins.pull_up = sys_cpu_to_be16(pins->pull_up);
	pin_buf.pins.pull_down = sys_cpu_to_be16(pins->pull_down);
	pin_buf.pins.open_drain = sys_cpu_to_be16(pins->open_drain);
	pin_buf.pins.polarity = sys_cpu_to_be16(pins->polarity);
	pin_buf.pins.dir = sys_cpu_to_be16(pins->dir);
	pin_buf.pins.data = sys_cpu_to_be16(pins->data);

	LOG_DBG("CFG %u %x : ID %04x ; PU %04x ; PD %04x ; DIR %04x ; DAT %04x",
		pin, flags,
@@ -211,14 +336,32 @@ static int sx1509b_config(struct device *dev,
					   SX1509B_REG_DATA, pins->data);
		if (rc == 0) {
			rc = i2c_write(drv_data->i2c_master,
				       &outbuf.reg,
				       sizeof(outbuf) - sizeof(pins->data),
				       &pin_buf.reg,
				       sizeof(pin_buf) - sizeof(pins->data),
				       cfg->i2c_slave_addr);
		}
	} else {
		rc = i2c_write(drv_data->i2c_master,
			       &outbuf.reg,
			       sizeof(outbuf),
			       &pin_buf.reg,
			       sizeof(pin_buf),
			       cfg->i2c_slave_addr);
	}

	if (rc == 0) {
		struct {
			u8_t reg;
			struct sx1509b_debounce_state debounce;
		} __packed debounce_buf;

		debounce_buf.reg = SX1509B_REG_DEBOUNCE_CONFIG;
		debounce_buf.debounce.debounce_config
			= debounce->debounce_config;
		debounce_buf.debounce.debounce_enable
			= sys_cpu_to_be16(debounce->debounce_enable);

		rc = i2c_write(drv_data->i2c_master,
			       &debounce_buf.reg,
			       sizeof(debounce_buf),
			       cfg->i2c_slave_addr);
	}

@@ -320,12 +463,70 @@ static int pin_interrupt_configure(struct device *dev,
				   enum gpio_int_mode mode,
				   enum gpio_int_trig trig)
{
	int ret = 0;
	int rc = 0;

	if (mode != GPIO_INT_MODE_DISABLED) {
		ret = -ENOTSUP;
	if (!IS_ENABLED(CONFIG_GPIO_SX1509B_INTERRUPT)
	    && (mode != GPIO_INT_MODE_DISABLED)) {
		return -ENOTSUP;
	}
	return ret;

#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
	/* Device does not support level-triggered interrupts. */
	if (mode == GPIO_INT_MODE_LEVEL) {
		return -ENOTSUP;
	}

	const struct sx1509b_config *cfg = dev->config->config_info;
	struct sx1509b_drv_data *drv_data = dev->driver_data;
	struct sx1509b_irq_state *irq = &drv_data->irq_state;
	struct {
		u8_t reg;
		struct sx1509b_irq_state irq;
	} __packed irq_buf;

	/* Only level triggered interrupts are supported, and those
	 * only if interrupt support is enabled.
	 */
	if (IS_ENABLED(CONFIG_GPIO_SX1509B_INTERRUPT)) {
		if (mode == GPIO_INT_MODE_LEVEL) {
			return -ENOTSUP;
		}
	} else if (mode != GPIO_INT_MODE_DISABLED) {
		return -ENOTSUP;
	}

	k_sem_take(&drv_data->lock, K_FOREVER);

	irq->interrupt_sense &= ~(SX1509B_EDGE_BOTH << (pin * 2));
	if (mode == GPIO_INT_MODE_DISABLED) {
		drv_data->cb_pins &= ~BIT(pin);
		irq->interrupt_mask |= BIT(pin);
	} else { /* GPIO_INT_MODE_EDGE */
		drv_data->cb_pins |= BIT(pin);
		irq->interrupt_mask &= ~BIT(pin);
		if (trig == GPIO_INT_TRIG_BOTH) {
			irq->interrupt_sense |= (SX1509B_EDGE_BOTH <<
								(pin * 2));
		} else if (trig == GPIO_INT_TRIG_LOW) {
			irq->interrupt_sense |= (SX1509B_EDGE_FALLING <<
								(pin * 2));
		} else if (trig == GPIO_INT_TRIG_HIGH) {
			irq->interrupt_sense |= (SX1509B_EDGE_RISING <<
								(pin * 2));
		}
	}

	irq_buf.reg = SX1509B_REG_INTERRUPT_MASK;
	irq_buf.irq.interrupt_mask = sys_cpu_to_be16(irq->interrupt_mask);
	irq_buf.irq.interrupt_sense = sys_cpu_to_be32(irq->interrupt_sense);

	rc = i2c_write(drv_data->i2c_master, &irq_buf.reg, sizeof(irq_buf),
		       cfg->i2c_slave_addr);

	k_sem_give(&drv_data->lock);
#endif /* CONFIG_GPIO_SX1509B_INTERRUPT */

	return rc;
}

/**
@@ -348,6 +549,31 @@ static int sx1509b_init(struct device *dev)
		goto out;
	}

#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
	drv_data->dev = dev;

	drv_data->gpio_int = device_get_binding(cfg->gpio_int_dev_name);
	if (!drv_data->gpio_int) {
		rc = -ENOTSUP;
		goto out;
	}
	k_work_init(&drv_data->work, sx1509b_work_handler);

	gpio_pin_configure(drv_data->gpio_int, cfg->gpio_pin,
			   GPIO_INPUT | cfg->gpio_flags);
	gpio_pin_interrupt_configure(drv_data->gpio_int, cfg->gpio_pin,
				     GPIO_INT_EDGE_TO_ACTIVE);

	gpio_init_callback(&drv_data->gpio_cb, sx1509_int_cb,
			   BIT(cfg->gpio_pin));
	gpio_add_callback(drv_data->gpio_int, &drv_data->gpio_cb);
	gpio_enable_callback(drv_data->gpio_int, cfg->gpio_pin);

	drv_data->irq_state = (struct sx1509b_irq_state) {
		.interrupt_mask = ALL_PINS,
	};
#endif

	rc = i2c_reg_write_byte(drv_data->i2c_master, cfg->i2c_slave_addr,
				SX1509B_REG_RESET, SX1509B_REG_RESET_MAGIC0);
	if (rc != 0) {
@@ -370,6 +596,9 @@ static int sx1509b_init(struct device *dev)
		.data = (ALL_PINS
			 & ~DT_INST_PROP(0, init_out_low)),
	};
	drv_data->debounce_state = (struct sx1509b_debounce_state) {
		.debounce_config = CONFIG_GPIO_SX1509B_DEBOUNCE_TIME,
	};

	rc = i2c_reg_write_byte(drv_data->i2c_master, cfg->i2c_slave_addr,
				SX1509B_REG_CLOCK,
@@ -400,6 +629,37 @@ out:
	return rc;
}

#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
static int gpio_sx1509b_manage_callback(struct device *dev,
					  struct gpio_callback *callback,
					  bool set)
{
	struct sx1509b_drv_data *data = dev->driver_data;

	return gpio_manage_callback(&data->cb, callback, set);
}

static int gpio_sx1509b_enable_callback(struct device *dev,
					  gpio_pin_t pin)
{
	struct sx1509b_drv_data *data = dev->driver_data;

	data->cb_pins |= BIT(pin);

	return 0;
}

static int gpio_sx1509b_disable_callback(struct device *dev,
					   gpio_pin_t pin)
{
	struct sx1509b_drv_data *data = dev->driver_data;

	data->cb_pins &= ~BIT(pin);

	return 0;
}
#endif

static const struct gpio_driver_api api_table = {
	.pin_configure = sx1509b_config,
	.port_get_raw = port_get,
@@ -408,6 +668,11 @@ static const struct gpio_driver_api api_table = {
	.port_clear_bits_raw = port_clear_bits,
	.port_toggle_bits = port_toggle_bits,
	.pin_interrupt_configure = pin_interrupt_configure,
#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
	.manage_callback = gpio_sx1509b_manage_callback,
	.enable_callback = gpio_sx1509b_enable_callback,
	.disable_callback = gpio_sx1509b_disable_callback,
#endif
};

static const struct sx1509b_config sx1509b_cfg = {
@@ -415,6 +680,11 @@ static const struct sx1509b_config sx1509b_cfg = {
		.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(0),
	},
	.i2c_master_dev_name = DT_INST_BUS_LABEL(0),
#ifdef CONFIG_GPIO_SX1509B_INTERRUPT
	.gpio_int_dev_name = DT_INST_GPIO_LABEL(0, nint_gpios),
	.gpio_pin = DT_INST_GPIO_PIN(0, nint_gpios),
	.gpio_flags = DT_INST_GPIO_FLAGS(0, nint_gpios),
#endif
	.i2c_slave_addr = DT_INST_REG_ADDR(0),
};

+7 −0
Original line number Diff line number Diff line
@@ -18,6 +18,13 @@ properties:
      required: true
      const: 16

    nint-gpios:
      type: phandle-array
      required: false
      description: |
        Connection for the NINT signal. This signal is active-low when
        produced by sx1509b GPIO node.

    init-out-low:
      type: int
      required: false