diff mbox series

[resend,v1,1/1] adv_mix955x is a scheme that multiplexes PCA9554/PCA9555 into LED and GPIO

Message ID d368a617c11446f2baff6c9cb06b24e4@ACNMB2.ACN.ADVANTECH.CORP (mailing list archive)
State Changes Requested, archived
Headers show
Series [resend,v1,1/1] adv_mix955x is a scheme that multiplexes PCA9554/PCA9555 into LED and GPIO | expand

Commit Message

yuechao.zhao(赵越超) April 27, 2021, 7:17 a.m. UTC
From: Yuechao Zhao <yuechao.zhao@advantech.com.cn>

With this driver, we can multiplex PCA9554/PCA9555 into LED and GPIO based on the ACPI data of BIOS.

Signed-off-by: Yuechao Zhao <yuechao.zhao@advantech.com.cn>
Signed-off-by: Sui Jia <jia.sui@advantech.com.cn>
---
 MAINTAINERS                        |   6 +
 drivers/platform/x86/Kconfig       |  14 +
 drivers/platform/x86/Makefile      |   3 +
 drivers/platform/x86/adv_mix955x.c | 703 +++++++++++++++++++++++++++++
 4 files changed, 726 insertions(+)
 create mode 100644 drivers/platform/x86/adv_mix955x.c

Comments

Randy Dunlap April 27, 2021, 7:34 a.m. UTC | #1
Hi--

On 4/27/21 12:17 AM, yuechao.zhao(赵越超) wrote:
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d92f85ca831d..3871c0d3bc6a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -9210,6 +9210,12 @@ S:	Maintained
>  F:	arch/x86/include/asm/intel_telemetry.h
>  F:	drivers/platform/x86/intel_telemetry*
>  
> +ADVANTECH MIX995X DRIVER
> +M:	Yuechao Zhao <yuechao.zhao@advantech.com.cn>
> +M:	Sui Jia <jia.sui@advantech.com.cn>
> +S:	Maintained
> +F:	drivers/platform/x86/adv_mix955x.c
> +

This new entry is way out of place. This file is supposed to be kept in
alphabetical order by the first line ("title"), that being
ADVANTECH MIX995X DRIVER

>  INTEL UNCORE FREQUENCY CONTROL
>  M:	Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
>  L:	platform-driver-x86@vger.kernel.org


thanks.
diff mbox series

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index d92f85ca831d..3871c0d3bc6a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9210,6 +9210,12 @@  S:	Maintained
 F:	arch/x86/include/asm/intel_telemetry.h
 F:	drivers/platform/x86/intel_telemetry*
 
+ADVANTECH MIX995X DRIVER
+M:	Yuechao Zhao <yuechao.zhao@advantech.com.cn>
+M:	Sui Jia <jia.sui@advantech.com.cn>
+S:	Maintained
+F:	drivers/platform/x86/adv_mix955x.c
+
 INTEL UNCORE FREQUENCY CONTROL
 M:	Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
 L:	platform-driver-x86@vger.kernel.org
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ad4e630e73e2..ccead75c764b 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -1284,6 +1284,20 @@  config INTEL_TELEMETRY
 	  directly via debugfs files. Various tools may use
 	  this interface for SoC state monitoring.
 
+config ADV_MIX955X
+    tristate "Advantech LEDs and GPIOs Driver"
+    depends on X86_64
+    depends on ACPI && GPIOLIB
+    select LEDS_CLASS
+    select LEDS_TRIGGERS
+    help
+      This driver is based on the kernel driver leds-pca955x
+      and gpio-pca953x but rewrite for PCA9554/PCA9555.
+      This driver requires LED data which is defined inside
+      ACPI DSDT or devicetree.
+      In addition, the pin that is not defined as LED will
+      be declared as GPIO.
+
 endif # X86_PLATFORM_DEVICES
 
 config PMC_ATOM
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 60d554073749..5ecad0ae925e 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -138,3 +138,6 @@  obj-$(CONFIG_INTEL_TELEMETRY)		+= intel_telemetry_core.o \
 					   intel_telemetry_pltdrv.o \
 					   intel_telemetry_debugfs.o
 obj-$(CONFIG_PMC_ATOM)			+= pmc_atom.o
+
+# Advantech
+obj-$(CONFIG_ADV_MIX955X)		+= adv_mix955x.o
diff --git a/drivers/platform/x86/adv_mix955x.c b/drivers/platform/x86/adv_mix955x.c
new file mode 100644
index 000000000000..99fc1427d4d9
--- /dev/null
+++ b/drivers/platform/x86/adv_mix955x.c
@@ -0,0 +1,703 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2019 Advantech Corp.
+ *
+ * Author:
+ *	Jia Sui <jia.sui@advantech.com.cn>
+ *	Yuechao Zhao <yuechao.zhao@advantech.com.cn>
+ *
+ * A driver for AMIX955<X>(Advantech mixed device based on PCA955X).
+ * This driver provides LED and GPIO functions.
+ *
+ * Supported devices:
+ *
+ *	Device		ACPI_DEVICE_ID	Description	7-bit slave address
+ *	--------	--------------	---------------	-------------------
+ *	AMIX9554	AHC0320		Base on PCA9554	0x20 .. 0x27
+ *	AMIX9555	AHC0321		Base on PCA9555	0x20 .. 0x27
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/fwnode.h>
+#include <linux/acpi.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/consumer.h>
+#include <asm/unaligned.h>
+
+#define VERSION "0.05"
+
+enum AMIX955X_LED {
+	AMIX955X_LED_ON = 0,
+	AMIX955X_LED_OFF = 1,
+};
+
+#define BANK_SIZE 8
+/*
+ * Only support pca9554 and pca9555,
+ * The max pin numbers is 16
+ * Defind MAX_BANK = 2
+ */
+#define MAX_BANK 2
+
+/* PCA9554 GPIO register */
+#define AMIX955X_INPUT 0x00
+#define AMIX955X_OUTPUT 0x01
+#define AMIX955X_INVERT 0x02
+#define AMIX955X_DIRECTION 0x03
+
+/* Default Value of GPIO register */
+#define AMIX955X_OUTPUT_DEFAULT 0xFF
+#define AMIX955X_DIRECTION_DEFAULT 0xFFFF
+
+enum chip_id {
+	amix9554,
+	amix9555,
+};
+
+struct chip_info_t {
+	u8 bits;
+	u8 slv_addr; /* 7-bit slave address mask */
+	int slv_addr_shift; /* Number of bits to ignore */ };
+
+static struct chip_info_t chip_info_tables[] = {
+	[amix9554] = {
+		.bits = 8,
+		.slv_addr = /* 0100xxx */ 0x20,
+		.slv_addr_shift = 3,
+	},
+	[amix9555] = {
+		.bits = 16,
+		.slv_addr = /* 0100xxx */ 0x20,
+		.slv_addr_shift = 3,
+	},
+};
+
+struct amix955x_reg_config {
+	int direction;
+	int output;
+	int input;
+	int invert;
+};
+
+static const struct amix955x_reg_config amix955x_regs = {
+	.direction = AMIX955X_DIRECTION,
+	.output = AMIX955X_OUTPUT,
+	.input = AMIX955X_INPUT,
+	.invert = AMIX955X_INVERT,
+};
+
+struct driver_data {
+	struct mutex lock; /* protect read/write PCA955X */
+	struct i2c_client *client;
+	struct chip_info_t *chip_info;
+	struct amix955x_led *leds;
+	struct amix955x_gpio *gpio;
+	int (*write_regs)(struct i2c_client *i2c, int reg, u8 *val);
+	int (*read_regs)(struct i2c_client *i2c, int reg, u8 *val); };
+
+struct amix955x_led {
+	struct driver_data *data;
+	struct led_classdev led_cdev;
+	int led_id;
+	char name[32];
+	const char *default_trigger;
+};
+
+struct amix955x_gpio {
+	u8 reg_output[MAX_BANK];
+	u8 reg_direction[MAX_BANK];
+	int gpio_id[MAX_BANK * BANK_SIZE];//the id list of gpio pin
+
+	struct gpio_chip gpio_chip;
+	const struct amix955x_reg_config *regs; };
+
+struct amix955x_platform_data {
+	struct driver_data *data;
+	struct amix955x_led *leds;
+	struct amix955x_gpio *gpio;
+	int num_leds;
+	int gpio_start;
+	int num_gpio;
+};
+
+static const struct acpi_device_id acpi_amix955x_match[] = {
+	{ "AHC0320", amix9554 },
+	{ "AHC0321", amix9555 },
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, acpi_amix955x_match);
+
+static int get_device_index(struct i2c_client *client,
+			    const struct i2c_device_id *id)
+{
+	const struct acpi_device_id *acpi_id;
+
+	if (id)
+		return id->driver_data;
+
+	acpi_id = acpi_match_device(acpi_amix955x_match, &client->dev);
+	if (!acpi_id)
+		return -ENODEV;
+
+	return acpi_id->driver_data;
+}
+
+static inline u8 amix955x_set_bit(u8 val, int led_id, enum AMIX955X_LED 
+state) {
+	led_id %= BANK_SIZE;
+
+	switch (state) {
+	case AMIX955X_LED_ON:
+		val &= ~(1 << led_id); //Clear bit
+		break;
+	case AMIX955X_LED_OFF:
+		val |= 1 << led_id; //Set bit
+		break;
+	}
+
+	return val;
+}
+
+static int amix955x_read_regs_8(struct i2c_client *client, int reg, u8 
+*val) {
+	int ret = i2c_smbus_read_byte_data(client, reg);
+	*val = ret;
+	return ret;
+}
+
+static int amix955x_read_regs_16(struct i2c_client *client, int reg, u8 
+*val) {
+	int ret = i2c_smbus_read_word_data(client, reg << 1);
+
+	val[0] = (u16)ret & 0xFF;
+	val[1] = (u16)ret >> 8;
+
+	return ret;
+}
+
+static int amix955x_read_regs(struct driver_data *data, int reg, u8 
+*val) {
+	int ret;
+
+	ret = data->read_regs(data->client, reg, val);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "failed reading register\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int amix955x_read_single(struct driver_data *data, int reg, u32 *val,
+				int off)
+{
+	int ret;
+	int bank_shift = fls((data->chip_info->bits - 1) / BANK_SIZE);
+	int offset = off / BANK_SIZE;
+
+	ret = i2c_smbus_read_byte_data(data->client,
+				       (reg << bank_shift) + offset);
+
+	*val = ret;
+
+	if (ret < 0)
+		dev_err(&data->client->dev, "failed reading register\n");
+
+	return ret;
+}
+
+static int amix955x_write_regs_8(struct i2c_client *client, int reg, u8 
+*val) {
+	return i2c_smbus_write_byte_data(client, reg, *val); }
+
+static int amix955x_write_regs_16(struct i2c_client *client, int reg, 
+u8 *val) {
+	__le16 word = cpu_to_le16(get_unaligned((u16 *)val));
+
+	return i2c_smbus_write_word_data(client, reg << 1, (__force u16)word); 
+}
+
+static int amix955x_write_regs(struct driver_data *data, int reg, u8 
+*val) {
+	int ret;
+
+	ret = data->write_regs(data->client, reg, val);
+	if (ret < 0) {
+		dev_err(&data->client->dev, "failed writing register\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int amix955x_write_single(struct driver_data *data, int reg, u32 val,
+				 int off)
+{
+	int ret;
+	int bank_shift = fls((data->chip_info->bits - 1) / BANK_SIZE);
+	int offset = off / BANK_SIZE;
+
+	ret = i2c_smbus_write_byte_data(data->client,
+					(reg << bank_shift) + offset, val);
+
+	if (ret < 0)
+		dev_err(&data->client->dev, "failed writing register\n");
+
+	return ret;
+}
+
+static int amix955x_led_set(struct led_classdev *led_cdev,
+			    enum led_brightness value)
+{
+	struct amix955x_led *led;
+	struct driver_data *data;
+	int led_id;
+	int ret = 0;
+	u32 reg_val = 0;
+
+	led = container_of(led_cdev, struct amix955x_led, led_cdev);
+	data = led->data;
+	led_id = led->led_id;
+
+	mutex_lock(&data->lock);
+
+	//get current value
+	ret = amix955x_read_single(data, AMIX955X_OUTPUT, &reg_val, led_id);
+	if (ret < 0)
+		goto set_failed;
+
+	//change bit
+	switch (value) {
+	case LED_OFF:
+		reg_val = amix955x_set_bit(reg_val, led_id, AMIX955X_LED_OFF);
+		break;
+	default:
+		reg_val = amix955x_set_bit(reg_val, led_id, AMIX955X_LED_ON);
+		break;
+	}
+
+	//set new value
+	ret = amix955x_write_single(data, AMIX955X_OUTPUT, reg_val, led_id);
+
+set_failed:
+	mutex_unlock(&data->lock);
+
+	return ret;
+}
+
+static int amix955x_gpio_direction_input(struct gpio_chip *gc, unsigned 
+int off) {
+	struct driver_data *data = gpiochip_get_data(gc);
+	u8 reg_val;
+	unsigned int index;
+	int ret;
+
+	index = data->gpio->gpio_id[off];
+
+	mutex_lock(&data->lock);
+	reg_val = data->gpio->reg_direction[index / BANK_SIZE]
+					| (1u << (index % BANK_SIZE));
+
+	ret = amix955x_write_single(data, data->gpio->regs->direction,
+				    reg_val, index);
+	if (ret)
+		goto exit;
+
+	data->gpio->reg_direction[index / BANK_SIZE] = reg_val;
+exit:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int amix955x_gpio_direction_output(struct gpio_chip *gc,
+					  unsigned int off, int val)
+{
+	struct driver_data *data = gpiochip_get_data(gc);
+	u8 reg_val;
+	unsigned int index;
+	int ret;
+
+	index = data->gpio->gpio_id[off];
+
+	mutex_lock(&data->lock);
+	if (val) {
+		reg_val = data->gpio->reg_output[index / BANK_SIZE]
+			| (1u << (index & BANK_SIZE));
+	} else {
+		reg_val = data->gpio->reg_output[index / BANK_SIZE]
+			& ~(1u << (index % BANK_SIZE));
+	}
+
+	ret = amix955x_write_single(data, data->gpio->regs->output,
+				    reg_val, index);
+	if (ret)
+		goto exit;
+
+	data->gpio->reg_output[index / BANK_SIZE] = reg_val;
+
+	reg_val = data->gpio->reg_direction[index / BANK_SIZE]
+					& ~(1u << (index % BANK_SIZE));
+
+	ret = amix955x_write_single(data, data->gpio->regs->direction,
+				    reg_val, index);
+	if (ret)
+		goto exit;
+
+	data->gpio->reg_direction[index / BANK_SIZE] = reg_val;
+exit:
+	mutex_unlock(&data->lock);
+	return ret;
+}
+
+static int amix955x_gpio_get_value(struct gpio_chip *gc, unsigned int 
+off) {
+	struct driver_data *data = gpiochip_get_data(gc);
+	u32 reg_val;
+	int ret;
+	unsigned int index;
+
+	index = data->gpio->gpio_id[off];
+
+	mutex_lock(&data->lock);
+	ret = amix955x_read_single(data, data->gpio->regs->input,
+				   &reg_val, index);
+	mutex_unlock(&data->lock);
+
+	if (ret < 0)
+		return 0;
+
+	return (reg_val & (1u << (index % BANK_SIZE))) ? 1 : 0; }
+
+static void amix955x_gpio_set_value(struct gpio_chip *gc, unsigned int off,
+				    int val)
+{
+	struct driver_data *data = gpiochip_get_data(gc);
+	u8 reg_val;
+	int ret;
+	unsigned int index;
+
+	index = data->gpio->gpio_id[off];
+
+	mutex_lock(&data->lock);
+	if (val)
+		reg_val = data->gpio->reg_output[index / BANK_SIZE]
+						| (1u << (index % BANK_SIZE));
+	else
+		reg_val = data->gpio->reg_output[index / BANK_SIZE]
+						& ~(1u << (index % BANK_SIZE));
+
+	ret = amix955x_write_single(data, data->gpio->regs->output,
+				    reg_val, index);
+	if (ret)
+		goto exit;
+
+	data->gpio->reg_output[index / BANK_SIZE] = reg_val;
+
+exit:
+	mutex_unlock(&data->lock);
+}
+
+static int amix955x_gpio_get_direction(struct gpio_chip *gc, unsigned 
+int off) {
+	struct driver_data *data = gpiochip_get_data(gc);
+	u32 reg_val;
+	int ret;
+	unsigned int index;
+
+	index = data->gpio->gpio_id[off];
+
+	mutex_lock(&data->lock);
+	ret = amix955x_read_single(data, data->gpio->regs->direction,
+				   &reg_val, index);
+	mutex_unlock(&data->lock);
+	if (ret < 0)
+		return ret;
+
+	return !!(reg_val & (1u << (index % BANK_SIZE))); }
+
+static void amix955x_setup_gpio(struct amix955x_platform_data *pdata,
+				struct driver_data *data)
+{
+	struct gpio_chip *gc;
+
+	gc = &pdata->gpio->gpio_chip;
+
+	gc->direction_input = amix955x_gpio_direction_input;
+	gc->direction_output = amix955x_gpio_direction_output;
+	gc->get = amix955x_gpio_get_value;
+	gc->set = amix955x_gpio_set_value;
+	gc->get_direction = amix955x_gpio_get_direction;
+	//gc->set_config
+	//gc->set_multiple
+	gc->can_sleep = true;
+
+	gc->base = pdata->gpio_start;
+	gc->ngpio = pdata->num_gpio;
+
+	gc->label = dev_name(&data->client->dev);
+	gc->parent = &data->client->dev;
+
+	gc->owner = THIS_MODULE;
+}
+
+static struct amix955x_platform_data *
+amix955x_get_pdata(struct i2c_client *client, struct chip_info_t *chip) 
+{
+	struct amix955x_platform_data *pdata;
+	struct fwnode_handle *child;
+	u32 bitmask = 0;
+	int pin_id = 0;
+	int count = 0;
+	int index = 0;
+
+	count = device_get_child_node_count(&client->dev);
+	if (count > chip->bits)
+		return ERR_PTR(-ENODEV);
+
+	pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return ERR_PTR(-ENOMEM);
+
+	pdata->num_leds = 0;
+	//BIOS declares that the LED information is in the ACPI table
+	if (count > 0) {
+		pdata->leds = devm_kcalloc(&client->dev, count,
+					   sizeof(struct amix955x_led),
+					   GFP_KERNEL);
+		if (!pdata->leds)
+			return ERR_PTR(-ENOMEM);
+
+		device_for_each_child_node(&client->dev, child) {
+			const char *name;
+			u32 reg;
+			int res;
+
+			res = fwnode_property_read_u32(child, "reg", &reg);
+			if (res != 0 || reg >= chip->bits)
+				continue;
+
+			pdata->leds[index].led_id = reg;
+			bitmask |= 1 << reg;
+
+			res = fwnode_property_read_string(child, "label",
+							  &name);
+			if (res != 0 && is_of_node(child))
+				name = to_of_node(child)->name;
+
+			snprintf(pdata->leds[index].name,
+				 sizeof(pdata->leds[index].name), "%s", name);
+
+			index++;
+		}
+
+		pdata->num_leds = count;
+	}
+
+	//No pin needs to be initialized to gpio
+	if (count == chip->bits)
+		return pdata;
+
+	pdata->gpio = devm_kzalloc(&client->dev, sizeof(struct amix955x_gpio),
+				   GFP_KERNEL);
+	if (!pdata->gpio)
+		return ERR_PTR(-ENOMEM);
+
+	for (pin_id = 0, index = 0; pin_id < chip->bits; pin_id++) {
+		//this pin is used be led
+		if ((bitmask >> pin_id) & 0x01)
+			continue;
+
+		pdata->gpio->gpio_id[index] = pin_id;
+		index++;
+	}
+
+	pdata->num_gpio = index;
+
+	/*
+	 * Please pass -1 as base to let gpiolib select the chip base
+	 * in all possible cases
+	 */
+	pdata->gpio_start = -1;
+
+	return pdata;
+}
+
+static void
+amix955x_set_direction_as_output(struct amix955x_platform_data *pdata) 
+{
+	u8 i;
+	u16 val = AMIX955X_DIRECTION_DEFAULT;
+	struct driver_data *data = pdata->data;
+	struct i2c_client *client = data->client;
+
+	for (i = 0; i < pdata->num_leds; i++)
+		val &= ~(1 << pdata->leds[i].led_id);
+
+	data->write_regs(client, AMIX955X_DIRECTION, (u8 *)&val); }
+
+static void amix955x_init_output_reg(struct driver_data *data) {
+	u8 val[MAX_BANK] = {AMIX955X_OUTPUT_DEFAULT};
+	struct i2c_client *client = data->client;
+
+	data->write_regs(client, AMIX955X_OUTPUT, val); }
+
+static int device_amix955x_init(struct amix955x_platform_data *pdata) {
+	int ret;
+	u8 val[MAX_BANK] = {0};
+
+	pdata->gpio->regs = &amix955x_regs;
+
+	ret = amix955x_read_regs(pdata->data, pdata->gpio->regs->output,
+				 pdata->gpio->reg_output);
+	if (ret)
+		goto out;
+
+	ret = amix955x_read_regs(pdata->data, pdata->gpio->regs->direction,
+				 pdata->gpio->reg_direction);
+	if (ret)
+		goto out;
+
+	//not set invert
+	ret = amix955x_write_regs(pdata->data, AMIX955X_INVERT, val);
+out:
+	return ret;
+}
+
+static int amix955x_probe(struct i2c_client *client,
+			  const struct i2c_device_id *id)
+{
+	struct driver_data *data;
+	struct chip_info_t *chip;
+	int ret;
+	int i, err;
+	struct amix955x_platform_data *pdata;
+
+	int device_id = 0;
+
+	device_id = get_device_index(client, id);
+	if (device_id < 0 || device_id >= ARRAY_SIZE(chip_info_tables))
+		return -ENODEV;
+
+	chip = &chip_info_tables[device_id];
+
+	pdata = amix955x_get_pdata(client, chip);
+	if (IS_ERR(pdata))
+		return PTR_ERR(pdata);
+
+	/* Make sure the slave address given is possible */
+	if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) !=
+	    chip->slv_addr) {
+		dev_err(&client->dev, "invalid slave address %02x\n",
+			client->addr);
+		return -ENODEV;
+	}
+
+	dev_info(&client->dev,
+		 "%s: Using %s %d-bit LED driver at slave address 0x%02x\n",
+		 client->dev.driver->name, client->name, chip->bits,
+		 client->addr);
+
+	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+		return -EIO;
+
+	//Initial driver data
+	data = devm_kzalloc(&client->dev, sizeof(struct driver_data),
+			    GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	mutex_init(&data->lock);
+
+	data->leds = pdata->leds;
+	data->gpio = pdata->gpio;
+	data->client = client;
+	data->chip_info = chip;
+
+	switch (chip->bits) {
+	case 8:
+		data->read_regs = amix955x_read_regs_8;
+		data->write_regs = amix955x_write_regs_8;
+		break;
+	case 16:
+		data->read_regs = amix955x_read_regs_16;
+		data->write_regs = amix955x_write_regs_16;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	pdata->data = data;
+
+	i2c_set_clientdata(client, data);
+
+	/* Set GPIO direction as output */
+	amix955x_set_direction_as_output(pdata);
+
+	/* Turn off all LEDs */
+	amix955x_init_output_reg(data);
+
+	for (i = 0; i < pdata->num_leds; i++) {
+		struct amix955x_led *led;
+		struct led_classdev *led_cdev;
+
+		led = &data->leds[i];
+		led_cdev = &led->led_cdev;
+
+		led->data = data;
+
+		led_cdev->name = led->name;
+		led_cdev->brightness_set_blocking = amix955x_led_set;
+
+		if (pdata->leds[i].default_trigger) {
+			led_cdev->default_trigger =
+				pdata->leds[i].default_trigger;
+		}
+
+		err = devm_led_classdev_register(&client->dev, led_cdev);
+		if (err)
+			return err;
+
+		amix955x_led_set(led_cdev, LED_OFF);
+	}
+
+	amix955x_setup_gpio(pdata, data);
+
+	ret = device_amix955x_init(pdata);
+	if (ret < 0)
+		return ret;
+
+	ret = devm_gpiochip_add_data(&client->dev, &data->gpio->gpio_chip,
+				     data);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct i2c_driver amix955x_driver = {
+	.driver = {
+		.name = "amix955x",
+		.acpi_match_table = ACPI_PTR(acpi_amix955x_match),
+	},
+	.probe = amix955x_probe,
+};
+
+module_i2c_driver(amix955x_driver);
+
+MODULE_AUTHOR("Jia Sui <jia.sui@advantech.com.cn>"); 
+MODULE_DESCRIPTION("AMIX955X LED and GPIO driver"); MODULE_LICENSE("GPL 
+v2"); MODULE_VERSION(VERSION);
--
2.18.2