/*
 * GPIO Chip driver for PCAL6408 I/O Expander
 *
 * Copyright 2017 SECO srl
 *
 * Licensed under the GPL-2 or later.
 */
 
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/acpi.h>
#include <linux/interrupt.h>
#include <asm/hw_irq.h>

#define DRV_NAME	"PCAL6408"

#define NR_GPIO    8
#define CAN_SLEEP  0
/*
 * PCAL6408 registers used by the GPIO device
 */
#define PCAL6408_INPUT_PORT		0x00
#define PCAL6408_OUTPUT_PORT		0x01
#define PCAL6408_POL_INV		0x02
#define PCAL6408_CONFIG			0x03
#define PCAL6408_OUTPUT_DRV_0		0x40
#define PCAL6408_OUTPUT_DRV_1		0x41
#define PCAL6408_INPUT_LATCH		0x42
#define PCAL6408_PULL_ENABLE		0x43
#define PCAL6408_PULL_SEL		0x44
#define PCAL6408_INT_MASK		0x45
#define PCAL6408_INT_STATUS		0x46
#define PCAL6408_OUTPUT_PORT_CFG	0x4F

//#define CONFIG_GPIO_EXP_IRQ 1

/*
 * Debug Macros
 */
#define CGPIO_INFO(fmt, arg...) printk(KERN_INFO "SecoCGPIO: " fmt "\n" , ## arg)
#define CGPIO_ERR(fmt, arg...)  printk(KERN_ERR "%s: " fmt "\n" , __func__ , ## arg)
#define CGPIO_DBG(fmt, arg...)  pr_debug("%s: " fmt "\n" , __func__ , ## arg)


struct gpio_exp_chip {
	struct gpio_chip gpio_chip;
	struct i2c_client *client;
	struct mutex i2c_lock;
	struct mutex lock;
	struct mutex lock_irq;
	uint8_t cache[8];
	struct irq_domain *irq_domain;
	int irq;
	u8 reg_io_dir;
	u8 reg_output;
};

static int gpio_exp_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
{
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);

	mutex_lock(&chip->i2c_lock);
	chip->reg_io_dir &= ~BIT(offset);
	i2c_smbus_write_byte_data(chip->client, PCAL6408_CONFIG, ~chip->reg_io_dir);
	mutex_unlock(&chip->i2c_lock);

	return 0;
}

static int gpio_exp_gpio_direction_output(struct gpio_chip *gc,
		unsigned offset, int val)
{
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);

	mutex_lock(&chip->i2c_lock);
	chip->reg_io_dir |=  BIT(offset);
	i2c_smbus_write_byte_data(chip->client, PCAL6408_CONFIG, ~chip->reg_io_dir);
	mutex_unlock(&chip->i2c_lock);

	return 0;
}

static int gpio_exp_gpio_get_direction(struct gpio_chip *gc, unsigned offset)
{
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);
	return (chip->reg_io_dir & BIT(offset)) == 0;
}

static int gpio_exp_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);
	u8 reg;

	mutex_lock(&chip->i2c_lock);
	if((chip->reg_io_dir & BIT(offset)) != 0)
		reg = i2c_smbus_read_byte_data(chip->client, PCAL6408_OUTPUT_PORT);
	else
		reg = i2c_smbus_read_byte_data(chip->client, PCAL6408_INPUT_PORT);
	mutex_unlock(&chip->i2c_lock);

	return (reg & BIT(offset)) != 0;
}

static void gpio_exp_gpio_set_value(struct gpio_chip *gc, unsigned offset, int val)
{
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);

	mutex_lock(&chip->i2c_lock);

	if (val)
		chip->reg_output |= BIT(offset);
	else
		chip->reg_output &= ~BIT(offset);

	i2c_smbus_write_byte_data(chip->client, PCAL6408_OUTPUT_PORT,
				  chip->reg_output);
	mutex_unlock(&chip->i2c_lock);
}

static void gpio_exp_gpio_set_multiple(struct gpio_chip *gc,
		unsigned long *mask, unsigned long *bits)
{
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);

	mutex_lock(&chip->i2c_lock);
	chip->reg_output = (chip->reg_output & ~mask[0]) | bits[0];
	i2c_smbus_write_byte_data(chip->client, PCAL6408_OUTPUT_PORT,
				  chip->reg_output);
	mutex_unlock(&chip->i2c_lock);
}

#ifdef CONFIG_GPIO_EXP_IRQ

static struct lock_class_key gpio_lock_class;

static int gpio_exp_exp_gpio_to_irq (struct gpio_chip *chip, unsigned offset) {
	struct gpio_exp_chip *pcal_chip;
	pcal_chip = container_of (chip, struct gpio_exp_chip, gpio_chip);	
	return irq_find_mapping(pcal_chip->irq_domain, offset);
}

static void gpio_exp_exp_irq_bus_lock (struct irq_data *data) {
	struct gpio_exp_chip *pcal_chip;
	pcal_chip = irq_data_get_irq_chip_data(data);
	mutex_lock (&pcal_chip->lock_irq);
}

static void gpio_exp_exp_irq_bus_unlock (struct irq_data *data) {
	struct gpio_exp_chip *pcal_chip;
	pcal_chip = irq_data_get_irq_chip_data(data);
	mutex_unlock (&pcal_chip->lock_irq);
}

static void gpio_exp_exp_irq_mask (struct irq_data *data) {
	struct gpio_exp_chip *pcal_chip;
	unsigned int pos;

	pcal_chip = irq_data_get_irq_chip_data(data);
	pos = data->hwirq;
}

static void gpio_exp_exp_irq_unmask (struct irq_data *data) {
	struct gpio_exp_chip *pcal_chip;
	unsigned int pos;

	pcal_chip = irq_data_get_irq_chip_data(data);
	pos = data->hwirq;
}

static int gpio_exp_exp_irq_set_type (struct irq_data *data, unsigned int type) {
	struct gpio_exp_chip *pcal_chip;
	int status = 0;

	pcal_chip = irq_data_get_irq_chip_data(data);	
	return status;
}

static void gpio_exp_exp_free_irq (struct gpio_exp_chip *chip) {
	unsigned int irq, i;

	for ( i = 0; i < chip->gpio_chip.ngpio ; i++ ) {
		irq = irq_find_mapping (chip->irq_domain, i);
		if ( irq > 0 )
			irq_dispose_mapping (irq);
	}

	irq_domain_remove (chip->irq_domain);
}

static unsigned int gpio_exp_exp_irq_startup (struct irq_data *data) {
	struct gpio_exp_chip *pcal_chip;

	pcal_chip = irq_data_get_irq_chip_data(data);

	gpio_exp_exp_irq_unmask (data);
	return 0;
}

static void gpio_exp_exp_irq_shutdown (struct irq_data *data) {
	struct gpio_exp_chip *pcal_chip;

	pcal_chip = irq_data_get_irq_chip_data(data);
	gpio_exp_exp_irq_mask (data);
}

static struct irq_chip pcal_exp_irq_chip = {
	.name								= "pcal-exp-seco",
	.irq_mask						= gpio_exp_exp_irq_mask,
	.irq_unmask					= gpio_exp_exp_irq_unmask,
	.irq_set_type					= gpio_exp_exp_irq_set_type,
	.irq_bus_lock					= gpio_exp_exp_irq_bus_lock,
	.irq_bus_sync_unlock	= gpio_exp_exp_irq_bus_unlock,
	.irq_startup					= gpio_exp_exp_irq_startup,
	.irq_shutdown				= gpio_exp_exp_irq_shutdown,
};

static irqreturn_t gpio_exp_irq_handler(int irq, void *dev)
{
	int reg_interrupt;
	unsigned int child_irq, i;
	struct gpio_chip *gc = (struct gpio_chip *)(dev);
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);

	mutex_lock(&chip->i2c_lock);

	reg_interrupt = i2c_smbus_read_byte_data(chip->client, PCAL6408_INPUT_PORT);
	if(reg_interrupt < 0)
		CGPIO_ERR("pcal6408_irq_handler i2c_smbus_read_byte_data failer with ret=%d", reg_interrupt);
		
	mutex_unlock(&chip->i2c_lock);

	for ( i = 0 ; i < chip->gpio_chip.ngpio ; i++ ) {
		if ( !(BIT(i) & reg_interrupt) ) {
			child_irq = irq_find_mapping (chip->irq_domain, i);
			handle_nested_irq (child_irq);
			CGPIO_INFO("pcal6408_irq_handler handle_nested_irq reg_interrupt = 0x%02X loop = %d child_irq= %d", reg_interrupt, i, child_irq);
		}
	}
	
	return IRQ_HANDLED;
}

static int gpio_exp_irq_setup(struct gpio_exp_chip *chip)
{
	int err, irq, i, done;

	chip->irq_domain = irq_domain_add_linear(NULL, chip->gpio_chip.ngpio,
								&irq_domain_simple_ops, chip);
	if ( !chip->irq_domain ) {
		CGPIO_ERR ("cannot assign irq domain");
		err = -ENODEV;
		goto err_irq_domain_add;
	}


	err = devm_request_threaded_irq(&chip->client->dev, chip->client->irq, NULL, gpio_exp_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&chip->client->dev), chip);
	if (err != 0) {
		CGPIO_ERR ("pcal6408_irq_setup unable to request IRQ#%d: %d", chip->client->irq, err);
		goto err_request_thr_irq;
	}

	chip->gpio_chip.to_irq = gpio_exp_exp_gpio_to_irq;

	for ( i = 0 ; i < chip->gpio_chip.ngpio ; i++ ) {
		irq = irq_create_mapping (chip->irq_domain, i);
		if ( irq < 0 ) {
			CGPIO_ERR ("cannot map irq");
			done = i;
			err = -EINVAL;
			goto err_map_irq;
		}
		irq_set_lockdep_class (irq, &gpio_lock_class);
		irq_set_chip_data (irq, chip);
		irq_set_chip (irq, &pcal_exp_irq_chip);
		irq_set_nested_thread (irq, true);
	}

	return 0;

err_map_irq:
	while ( --done ) {
		irq = irq_find_mapping (chip->irq_domain, i);
		if ( irq > 0 )
			irq_dispose_mapping (irq);
	}
err_request_thr_irq:
	irq_domain_remove (chip->irq_domain);
err_irq_domain_add:
	CGPIO_ERR("-pcal6408_irq_setup FAILED");
	return err;

}

#else

static irqreturn_t gpio_exp_irq_handler(int irq, void *dev)
{
	int reg_interrupt;
	struct gpio_chip *gc = (struct gpio_chip *)(dev);
	struct gpio_exp_chip *chip = gpiochip_get_data(gc);

	mutex_lock(&chip->i2c_lock);

	reg_interrupt = i2c_smbus_read_byte_data(chip->client, PCAL6408_INPUT_PORT);
	if(reg_interrupt < 0)
		CGPIO_ERR("gpio_exp_irq_handler i2c_smbus_read_byte_data failer with ret=%d", reg_interrupt);
		
	mutex_unlock(&chip->i2c_lock);
	
	return IRQ_HANDLED;
}

static int gpio_exp_irq_setup(struct gpio_exp_chip *chip)
{	
	int err;

	chip->irq_domain = irq_domain_add_linear(NULL, chip->gpio_chip.ngpio,
								&irq_domain_simple_ops, chip);
	if ( !chip->irq_domain ) {
		CGPIO_ERR ("cannot assign irq domain");
		err = -ENODEV;
		goto err_irq_domain_add;
	}


	err = devm_request_threaded_irq(&chip->client->dev, chip->client->irq, NULL, gpio_exp_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&chip->client->dev), chip);
	if (err != 0) {
		CGPIO_ERR ("gpio_exp_irq_setup unable to request IRQ#%d: %d", chip->client->irq, err);
		goto err_request_thr_irq;
	}

	return 0;

err_request_thr_irq:
	irq_domain_remove (chip->irq_domain);
err_irq_domain_add:
	CGPIO_ERR("-gpio_exp_irq_setup FAILED");
	return err;
}
#endif /* CONFIG_GPIO_EXP_IRQ */


static int gpio_exp_exp_setup (struct gpio_exp_chip *chip, struct device *dev,
							unsigned base) {
	int err, status;

	/* init gpio chip structure */
	chip->gpio_chip.get = gpio_exp_gpio_get_value;
	chip->gpio_chip.set = gpio_exp_gpio_set_value;
	chip->gpio_chip.direction_input = gpio_exp_gpio_direction_input;
	chip->gpio_chip.direction_output = gpio_exp_gpio_direction_output;

	chip->gpio_chip.get_direction = gpio_exp_gpio_get_direction;
	chip->gpio_chip.set_multiple = gpio_exp_gpio_set_multiple;

	chip->gpio_chip.ngpio = NR_GPIO;
	chip->gpio_chip.label = chip->client->name;

	chip->gpio_chip.base = base;
	chip->gpio_chip.can_sleep = CAN_SLEEP;
	chip->gpio_chip.parent = dev;
	chip->gpio_chip.owner = THIS_MODULE;

	mutex_init (&chip->lock);
	mutex_init (&chip->lock_irq);

	/* add this gpio bank */
	status = gpiochip_add_data(&chip->gpio_chip, chip);
	if ( status < 0 ) {
		CGPIO_ERR ("cannot add gpio bank to the system");
		err = status;
		goto err_gpio_add;
	}

	i2c_set_clientdata(chip->client, chip);

	status = gpio_exp_irq_setup (chip);
	if ( status ) {
		CGPIO_ERR ("gpio_exp_irq_setup cannot setup gpio irq");
		err = status;
		goto err_irq_setup;
	}

	return 0;
err_irq_setup:
err_gpio_add:
	CGPIO_ERR("-gpio_exp_exp_setup FAILED");
	return err;
}


static int gpio_exp_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
	struct gpio_exp_chip *chip;
	int err, ret;
	u8 device_id;
	unsigned base = -1;

	/* Check the device ID register to see if it's responding. */
	device_id = i2c_smbus_read_byte_data(client, PCAL6408_INPUT_PORT);
	if (device_id < 0) {
		dev_err(dev, "gpio_exp_probe returned %d\n", device_id);
		return device_id;
	}

	i2c_smbus_write_byte_data(client, PCAL6408_OUTPUT_DRV_0, 	0xFF);
	i2c_smbus_write_byte_data(client, PCAL6408_OUTPUT_DRV_1, 	0xFF);
	i2c_smbus_write_byte_data(client, PCAL6408_PULL_ENABLE, 	0xFF);
	i2c_smbus_write_byte_data(client, PCAL6408_PULL_SEL, 		0xFF);

	i2c_smbus_write_byte_data(client, PCAL6408_CONFIG, 		0xFF);
	i2c_smbus_write_byte_data(client, PCAL6408_OUTPUT_PORT, 	0x00);

	i2c_smbus_write_byte_data(client, PCAL6408_INPUT_LATCH, 	0xFF);
	i2c_smbus_write_byte_data(client, PCAL6408_INT_MASK, 		0x00);

	chip = devm_kzalloc(dev, sizeof(struct gpio_exp_chip), GFP_KERNEL);
	if (!chip) {
		CGPIO_ERR ("cannot allocate memory for structure data");
		err = -ENOMEM;
		goto err_data_allocate;
	}

	chip->client = client;

	mutex_init(&chip->i2c_lock);
	i2c_smbus_read_byte_data(client, PCAL6408_INPUT_PORT);
	chip->reg_io_dir = ~i2c_smbus_read_byte_data(client, PCAL6408_CONFIG);
	chip->reg_output = i2c_smbus_read_byte_data(client, PCAL6408_OUTPUT_PORT);
	

	ret = gpio_exp_exp_setup(chip, dev, base);
	if ( ret != 0 ) {
		CGPIO_ERR ("cannot driver setup");
		err = ret;
		goto err_setup;
	}

	return 0;

err_setup:
err_data_allocate:
	return err;
}

static int gpio_exp_remove(struct i2c_client *client)
{
	struct gpio_exp_chip *chip = i2c_get_clientdata(client);

#ifdef CONFIG_GPIO_EXP_IRQ
	gpio_exp_exp_free_irq (chip);
#endif
	gpiochip_remove(&chip->gpio_chip);

	return 0;
}


static const struct i2c_device_id gpio_exp_id[] = {
	{ DRV_NAME , 0 },
	{ },
};
MODULE_DEVICE_TABLE(i2c, gpio_exp_id);

static const struct of_device_id gpio_exp_of_match[] = {
	{ .compatible = "fcs,PCAL6408" },
	{ }
};
MODULE_DEVICE_TABLE(of, gpio_exp_of_match);

static struct acpi_device_id gpio_exp_acpi_match[] = {
		{DRV_NAME, 0},
		{ },
};
MODULE_DEVICE_TABLE(acpi, gpio_exp_acpi_match);

static struct i2c_driver gpio_exp_driver = {
	.driver = {
		.name	= DRV_NAME,
		.owner	= THIS_MODULE,
		.of_match_table = gpio_exp_of_match,
		.acpi_match_table = ACPI_PTR(gpio_exp_acpi_match),
	},
	.probe		= gpio_exp_probe,
	.remove		= gpio_exp_remove,
	.id_table	= gpio_exp_id,
};

module_i2c_driver(gpio_exp_driver);

MODULE_AUTHOR("Massimo Milanesi <m.massimomilanesi@gmail.com>");
MODULE_DESCRIPTION("GPIO expander driver for PCAL6408");
MODULE_LICENSE("GPL");
