From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from xmbghk7.mail.qq.com (xmbghk7.mail.qq.com [43.163.128.48]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1C8D8260580; Mon, 17 Nov 2025 09:36:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=43.163.128.48 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763372172; cv=none; b=pu+N5JhWD3pk37z1AGMi2eiuyBDqGMqZQR9ZUe05mNrPrLPuGJN2OFkFCnDwjWY/e9A5AQrsSRVUT1bh+WAFujlANezYsoqTU5/cbA7XLwqNagbnbiq6ZKjfB2dUWGcJppc96f69JXF00ehUI9a2CWa9xYsIvhqIIu5tfWsfcmk= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763372172; c=relaxed/simple; bh=VQn8OwjB4F5vcpf18Ciqz3SSVkXB2wUFLZlOkNKB3lI=; h=Message-ID:From:To:Cc:Subject:Date:MIME-Version; b=nafr+H0t+PQ34VyRLFdgHOwwlVNRswve4F9T9cIpU2q8Gj/XGHS+fWGa9P+NxnxV8sM5AVkyXdr/Nh7eiQTlOhZrMJUZOiMOWZ0KBvkGoVY9Q78HkJFcorbbbCC1z9bV/2ybQkZHsxDzBkL7AGghVBmegNKSlscqhsMxmCHakjM= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=qq.com; spf=pass smtp.mailfrom=qq.com; dkim=pass (1024-bit key) header.d=qq.com header.i=@qq.com header.b=LM0mo6Lw; arc=none smtp.client-ip=43.163.128.48 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=qq.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=qq.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=qq.com header.i=@qq.com header.b="LM0mo6Lw" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qq.com; s=s201512; t=1763372158; bh=8BLWfnuFh7LexEZGB72kN/nsAVAwQ0IJwfvv54AqD9c=; h=From:To:Cc:Subject:Date; b=LM0mo6LwV/6csTClBWd1L1Pn7vGdDQ4aSeEuRVuJfuDbiaLwpu3ba7Tz13+1yvriB ktryddSTK6J8kj4sE/KlhVhSpFuyhvH9eDpEAq7FiAY+1OBYD+IHOTVP3w42U2baRg J+D2EXrzZJkHIA79bfhmiW3krSlVfHjKegHrBvUI= Received: from localhost.localdomain ([58.34.102.26]) by newxmesmtplogicsvrszb20-0.qq.com (NewEsmtp) with SMTP id 8F790060; Mon, 17 Nov 2025 17:35:55 +0800 X-QQ-mid: xmsmtpt1763372155tdvlpyxnj Message-ID: X-QQ-XMAILINFO: MVlKP0emjWgkG9Pi3skmSYLpkM6TJZ127XSUlENjMijQkT+5gXM3AUlllotZYo 5Rgfr6Zm2THMGSJjhpdRYO3OuG39nt8M3SmQSv3ZtipUzCUhzwv/H6xtXHoumWeuj8FJGNig5gSn dsz+xU8mgjsdd7BU+SkFjTv8NO/Rb2sy4Lsfnj1fzoubt5i7mCPkIGekomTOkizuc/of2XjQnRHd p5IezL8l7SuyExoxaAkoHkD2sHNle6/fjzMiFBKclQdLK/K5l30rMahr76rwuac+XMWWxhqgGNJb TrUNQubZ1ICltcFB7yDTiZOUZik9SUHhnngADceUoCMAGEO496XBW9BeKrwI750NDh3gNplPCr9h EJ7OWW7ZNSTQG2dAZCQpR6ZISiz0+wev1PXn/G0N1ufh6VmMh88htrEO7qtaRfgroKRST5RZrIjc eMGJkZlz8t8ADGXW/msxzlPgxfTg/6lSEKsNDKOrLur3zdJctXBLat2/OJ2/Oii+25BqYEdcE2pj 5XWITL6rWz7MUwuCs7mvOu9UPizCxJmZFRcvs0Z6SvZ9+KhHgHvPNGW8g0V5EuJ/5Kgjrt5R7nsZ ecNt5Nz2judW4SlT7JZatZPfggnJ/2+f/fZDOVlUZ+b7E1832c3bZrieOI4MPWB7Q6D6OB4jZ5t8 xdpG2THyzoxE7UZ07cfxubsX6CF8DnfM2NawqSjfjAUbZzRvbcwFtNS5S1Eovhga0DoHtH0vbwvN CW1T1IzPjCk8B0EtKq7Z/sXwk1PmDRZIWKcUx72Xtge/ZKoKRXVn46FCL5hGTgmHWp33vOo9ucN0 ZlxMFdEMqafZttG1hX86YEjDZ0ev7n3NbvBIA7MmlAN0RtrrhwvJ1AxT5Pg1JFyTZgg96gkUSwVB jfHeTqOZFv2kyspaYKoov7Gf6/ByO/2gGcLLBWBFkdfk9gLjei0cJkWuiycOdUp2HM9OrZKRgcTy O054obs+CLwdjNsB39ovuG9jenCLrima30nKh+y6DwHBjGoqjc2A== X-QQ-XMRINFO: OWPUhxQsoeAVDbp3OJHYyFg= From: 429368636@qq.com To: lee@kernel.org Cc: pavel@kernel.org, linus.walleij@linaro.org, brgl@bgdev.pl, linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, linux-gpio@vger.kernel.org, zhangxinyu , zhangxinyu <429368636@qq.com> Subject: [PATCH] leds: add aw91xxx driver Date: Mon, 17 Nov 2025 17:35:52 +0800 X-OQ-MSGID: <20251117093552.174326-1-429368636@qq.com> X-Mailer: git-send-email 2.25.1 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: zhangxinyu This commit adds support for AWINIC AW91XXX 6-channel LED driver. The chip supports 6 PWM channels and is controlled with I2C. Signed-off-by: zhangxinyu <429368636@qq.com> --- drivers/leds/Kconfig | 11 + drivers/leds/Makefile | 1 + drivers/leds/leds-aw91xxx.c | 1865 +++++++++++++++++++++++++++++++++++ drivers/leds/leds-aw91xxx.h | 128 +++ 4 files changed, 2005 insertions(+) create mode 100644 drivers/leds/leds-aw91xxx.c create mode 100644 drivers/leds/leds-aw91xxx.h diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 06e6291be11b..523f7063e0c3 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -1020,6 +1020,17 @@ config LEDS_ACER_A500 This option enables support for the Power Button LED of Acer Iconia Tab A500. +config LEDS_AW91XX + tristate "LED support for Awinic AW91XX" + depends on LEDS_CLASS && I2C && OF + select REGMAP_I2C + help + This option enables support for the AW91xx 6-channel + LED driver. + + To compile this driver as a module, choose M here: the module + will be called leds-aw91xx. + source "drivers/leds/blink/Kconfig" comment "Flash and Torch LED drivers" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 9a0333ec1a86..361406be5923 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -98,6 +98,7 @@ obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_AW91XX) += leds-aw91xxx.o # Kinetic ExpressWire Protocol obj-$(CONFIG_LEDS_EXPRESSWIRE) += leds-expresswire.o diff --git a/drivers/leds/leds-aw91xxx.c b/drivers/leds/leds-aw91xxx.c new file mode 100644 index 000000000000..8d809f3e443b --- /dev/null +++ b/drivers/leds/leds-aw91xxx.c @@ -0,0 +1,1865 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * leds-aw91xxx.c aw91xxx led module + * + * Copyright (c) 2021 AWINIC Technology CO., LTD + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include "leds-aw91xxx.h" + +/****************************************************** + * + * Marco + * + ******************************************************/ +#define AW91XXX_I2C_NAME "aw91xxx_led" +#define HRTIMER_FRAME 20 + +#define AW91XXX_DRIVER_VERSION "v1.0.0" + +#define AW_I2C_RETRIES 5 +#define AW_I2C_RETRY_DELAY 5 +#define AW_READ_CHIPID_RETRIES 5 +#define AW_READ_CHIPID_RETRY_DELAY 5 + +#define REG_INPUT_P0 0x00 +#define REG_INPUT_P1 0x01 +#define REG_OUTPUT_P0 0x02 +#define REG_OUTPUT_P1 0x03 +#define REG_CONFIG_P0 0x04 +#define REG_CONFIG_P1 0x05 +#define REG_INT_P0 0x06 +#define REG_INT_P1 0x07 +#define REG_ID 0x10 +#define REG_CTRL 0x11 +#define REG_WORK_MODE_P0 0x12 +#define REG_WORK_MODE_P1 0x13 +#define REG_EN_BREATH 0x14 +#define REG_FADE_TIME 0x15 +#define REG_FULL_TIME 0x16 +#define REG_DLY0_BREATH 0x17 +#define REG_DLY1_BREATH 0x18 +#define REG_DLY2_BREATH 0x19 +#define REG_DLY3_BREATH 0x1a +#define REG_DLY4_BREATH 0x1b +#define REG_DLY5_BREATH 0x1c +#define REG_DIM00 0x20 +#define REG_DIM01 0x21 +#define REG_DIM02 0x22 +#define REG_DIM03 0x23 +#define REG_DIM04 0x24 +#define REG_DIM05 0x25 +#define REG_DIM06 0x26 +#define REG_DIM07 0x27 +#define REG_DIM08 0x28 +#define REG_DIM09 0x29 +#define REG_SWRST 0x7F +#define REG_81H 0x81 + +/* aw91xxx register read/write access */ +#define REG_NONE_ACCESS 0 +#define REG_RD_ACCESS (1 << 0) +#define REG_WR_ACCESS (1 << 1) +#define AW91XXX_REG_MAX 0xFF +#define AW91XXX_DIM_MAX 0x0A +#define AW91XXX_PORT_NUM (0x10) /* 16 */ +#define AW91XXX_WORK_LED_NUM 10 + +const unsigned char aw91xxx_reg_access[AW91XXX_REG_MAX] = { + [REG_INPUT_P0] = REG_RD_ACCESS, + [REG_INPUT_P1] = REG_RD_ACCESS, + [REG_OUTPUT_P0] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_OUTPUT_P1] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_CONFIG_P0] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_CONFIG_P1] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_INT_P0] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_INT_P1] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_ID] = REG_RD_ACCESS, + [REG_CTRL] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_WORK_MODE_P0] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_WORK_MODE_P1] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_EN_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_FADE_TIME] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_FULL_TIME] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DLY0_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DLY1_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DLY2_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DLY3_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DLY4_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DLY5_BREATH] = REG_RD_ACCESS | REG_WR_ACCESS, + [REG_DIM00] = REG_WR_ACCESS, + [REG_DIM01] = REG_WR_ACCESS, + [REG_DIM02] = REG_WR_ACCESS, + [REG_DIM03] = REG_WR_ACCESS, + [REG_DIM04] = REG_WR_ACCESS, + [REG_DIM05] = REG_WR_ACCESS, + [REG_DIM06] = REG_WR_ACCESS, + [REG_DIM07] = REG_WR_ACCESS, + [REG_DIM08] = REG_WR_ACCESS, + [REG_DIM09] = REG_WR_ACCESS, + [REG_SWRST] = REG_WR_ACCESS, +}; + +static KEY_STATE key_map[] = { +/* name code val */ + {"DEL", KEY_BACK, 0}, + {"BACK", KEY_BACK, 0}, + {"HOME", KEY_HOME, 0}, + {"HOME", KEY_HOME, 0}, + {"BACK", KEY_BACK, 0}, + {"Q", KEY_Q, 0}, + {"F1", KEY_F1, 0}, + {"F2", KEY_F2, 0}, + + {"W", KEY_W, 0}, + {"E", KEY_E, 0}, + {"R", KEY_R, 0}, + {"T", KEY_T, 0}, + {"Y", KEY_Y, 0}, + {"U", KEY_U, 0}, + {"F3", KEY_F3, 0}, + {"F4", KEY_F4, 0}, + + {"I", KEY_I, 0}, + {"O", KEY_O, 0}, + {"P", KEY_P, 0}, + {"J", KEY_J, 0}, + {"K", KEY_K, 0}, + {"L", KEY_L, 0}, + {"F5", KEY_F5, 0}, + {"F6", KEY_F6, 0}, + + {"A", KEY_A, 0}, + {"S", KEY_S, 0}, + {"D", KEY_D, 0}, + {"F", KEY_F, 0}, + {"G", KEY_G, 0}, + {"H", KEY_H, 0}, + {"F7", KEY_F7, 0}, + {"F8", KEY_F8, 0}, + + {"Caps Lock", KEY_CAPSLOCK, 0}, + {"Z", KEY_Z, 0}, + {"X", KEY_X, 0}, + {"C", KEY_C, 0}, + {"V", KEY_V, 0}, + {"B", KEY_B, 0}, + {"F9", KEY_F9, 0}, + {"F10", KEY_F10, 0}, + + {"N", KEY_N, 0}, + {"M", KEY_M, 0}, + {"Backspace", KEY_BACKSPACE, 0}, + {"Down", KEY_DOWN, 0}, + {"Up", KEY_UP, 0}, + {"ENTER", KEY_ENTER, 0}, + {"F11", KEY_F11, 0}, + {"F12", KEY_F12, 0}, + + {"ALT", KEY_ALTERASE, 0}, + {".", KEY_DOT, 0}, + {"Left", KEY_LEFT, 0}, + {"Right", KEY_RIGHT, 0}, + {"Space", KEY_SPACE, 0}, + {"Volup", KEY_VOLUMEUP, 0}, + {"KEY_STOP", KEY_STOP, 0}, + {"KEY_AGAIN", KEY_AGAIN, 0}, + + {"PROPS", KEY_PROPS, 0}, + {"UNDO", KEY_UNDO, 0}, + {"FRONT", KEY_FRONT, 0}, + {"COPY", KEY_COPY, 0}, + {"OPEN", KEY_OPEN, 0}, + {"PASTE", KEY_PASTE, 0}, + {"FIND", KEY_FIND, 0}, + {"CUT", KEY_CUT, 0}, + +}; + +/****************************************************** + * + * aw91xxx i2c write/read + * + ******************************************************/ +static int aw91xxx_i2c_write(struct aw91xxx *aw91xxx, + unsigned char reg_addr, unsigned char reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_smbus_write_byte_data(aw91xxx->i2c, reg_addr, reg_data); + if (ret < 0) + pr_err("%s: i2c_write cnt=%d error=%d\n", __func__, cnt, ret); + else + break; + + cnt++; + usleep_range(1000, 2000); + } + + return ret; +} + +static int aw91xxx_i2c_read(struct aw91xxx *aw91xxx, + unsigned char reg_addr, unsigned char *reg_data) +{ + int ret = -1; + unsigned char cnt = 0; + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_smbus_read_byte_data(aw91xxx->i2c, reg_addr); + if (ret < 0) { + pr_err("%s: i2c_read cnt=%d error=%d\n", __func__, cnt, ret); + } else { + *reg_data = ret; + break; + } + cnt++; + usleep_range(1000, 2000); + } + + return ret; +} + +static int aw91xxx_i2c_write_bits(struct aw91xxx *aw91xxx, + unsigned char reg_addr, unsigned char *buf, unsigned int len) +{ + int ret = -1; + unsigned char *data; + unsigned char cnt = 0; + + data = kmalloc(len+1, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + data[0] = reg_addr; + memcpy(&data[1], buf, len); + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_master_send(aw91xxx->i2c, data, len + 1); + if (ret < 0) + pr_err("%s: i2c master send [0x%x] error\n", __func__, aw91xxx->i2c->addr); + else + break; + cnt++; + usleep_range(1000, 2000); + } + + kfree(data); + + return ret; +} + +static int aw91xxx_i2c_read_bits(struct aw91xxx *aw91xxx, + unsigned char reg_addr, unsigned char *buf, unsigned int len) +{ + int ret = 0; + unsigned char *rdbuf = NULL; + unsigned char cnt = 0; + + struct i2c_msg msgs[] = { + { + .addr = aw91xxx->i2c->addr, + .flags = 0, + .len = 1, + .buf = ®_addr, + }, + { + .addr = aw91xxx->i2c->addr, + .flags = I2C_M_RD, + .len = len, + }, + }; + + rdbuf = kmalloc(len, GFP_KERNEL); + if (rdbuf == NULL) + return -ENOMEM; + + msgs[1].buf = rdbuf; + + while (cnt < AW_I2C_RETRIES) { + ret = i2c_transfer(aw91xxx->i2c->adapter, msgs, ARRAY_SIZE(msgs)); + if (ret < 0) + pr_err("msg %s i2c read error: %d\n", __func__, ret); + else + break; + cnt++; + usleep_range(1000, 2000); + } + + if (buf != NULL) + memcpy(buf, rdbuf, len); + + kfree(rdbuf); + + return ret; +} + + +/****************************************************** + * + * aw91xxx led + * + ******************************************************/ +static void aw91xxx_brightness_work(struct work_struct *work) +{ + struct aw91xxx *aw91xxx = container_of(work, struct aw91xxx, + brightness_work); + + unsigned char i; + + if (aw91xxx->cdev.brightness > aw91xxx->cdev.max_brightness) + aw91xxx->cdev.brightness = aw91xxx->cdev.max_brightness; + + aw91xxx_i2c_write(aw91xxx, REG_EN_BREATH, 0x00); + + aw91xxx_i2c_write(aw91xxx, REG_CTRL, 0x03); + + for (i = 0; i < AW91XXX_WORK_LED_NUM; i++) + aw91xxx_i2c_write(aw91xxx, REG_DIM00 + i, aw91xxx->cdev.brightness); +} + +static void aw91xxx_set_brightness(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct aw91xxx *aw91xxx = container_of(cdev, struct aw91xxx, cdev); + + aw91xxx->cdev.brightness = brightness; + + schedule_work(&aw91xxx->brightness_work); +} + +static void aw91xxc_led_blink(struct work_struct *work) +{ + struct aw91xxx *aw91xxx = container_of(work, struct aw91xxx, int_work.work); + + aw91xxx_i2c_write(aw91xxx, REG_CONFIG_P0, ((aw91xxx->blink >> 4) & 0x03)); + aw91xxx_i2c_write(aw91xxx, REG_CONFIG_P1, (aw91xxx->blink & 0x0f)); + + aw91xxx_i2c_write(aw91xxx, REG_FADE_TIME, (aw91xxx->fall_time<<3) | (aw91xxx->rise_time)); + aw91xxx_i2c_write(aw91xxx, REG_FULL_TIME, (aw91xxx->off_time<<3) | (aw91xxx->on_time)); + + + aw91xxx_i2c_write(aw91xxx, REG_CTRL, 0x80 | aw91xxx->imax); +} + +static void aw91xxx_led_blink(struct aw91xxx *aw91xxx, unsigned char blink, unsigned char delay) +{ + unsigned char i; + + aw91xxx->blink = blink; + + if (aw91xxx->cdev.brightness > aw91xxx->cdev.max_brightness) + aw91xxx->cdev.brightness = aw91xxx->cdev.max_brightness; + aw91xxx_i2c_write(aw91xxx, REG_WORK_MODE_P0, 0x00); + aw91xxx_i2c_write(aw91xxx, REG_WORK_MODE_P1, 0x00); + aw91xxx_i2c_write(aw91xxx, REG_EN_BREATH, blink); + + + if (blink) { + if (aw91xxx->vendor_id == 0xB) { + aw91xxx_i2c_write(aw91xxx, REG_CONFIG_P0, 0x03); + aw91xxx_i2c_write(aw91xxx, REG_CONFIG_P1, 0x0f); + + aw91xxx_i2c_write(aw91xxx, REG_FADE_TIME, + (aw91xxx->fall_time<<3) | (aw91xxx->rise_time)); + aw91xxx_i2c_write(aw91xxx, REG_FULL_TIME, + (aw91xxx->off_time<<3) | (aw91xxx->on_time)); + + for (i = 0; i < 6; i++) { + aw91xxx_i2c_write(aw91xxx, REG_DIM00 + i, aw91xxx->cdev.brightness); + aw91xxx_i2c_write(aw91xxx, REG_DLY0_BREATH + i, delay); + } + + aw91xxx_i2c_write(aw91xxx, REG_CTRL, 0x80 | aw91xxx->imax); + } else { + schedule_delayed_work(&aw91xxx->int_work, msecs_to_jiffies(delay * 315)); + } + } else { + aw91xxx_i2c_write(aw91xxx, REG_EN_BREATH, 0x00); + + aw91xxx_i2c_write(aw91xxx, REG_CTRL, 0x03); + + for (i = 0; i < 10; i++) { + aw91xxx_i2c_write(aw91xxx, REG_DIM00 + i, 0x00); + aw91xxx_i2c_write(aw91xxx, REG_DLY0_BREATH + i, 0); + } + } +} + +/***************************************************** + * + * device tree + * + *****************************************************/ +static int aw91xxx_parse_dt(struct device *dev, struct aw91xxx *aw91xxx, struct device_node *np) +{ + int ret = 0; + unsigned int val = 0; + + aw91xxx->reset_gpio = of_get_named_gpio(np, "reset-gpio", 0); + + aw91xxx->single_key_enable = false; + ret = of_property_read_u32(np, "aw91xxx,single_key_enable", &val); + if (ret) { + dev_err(aw91xxx->dev, + "%s: no aw91xxx,single_key_enable provided,The default function is not key\n", + __func__); + } else { + AW_DEBUG("%s:aw91xxx,single_key_enable provided ok, val = %d\n", __func__, val); + if (val == 1) + aw91xxx->single_key_enable = true; + } + + aw91xxx->matrix_key_enable = false; + ret = of_property_read_u32(np, "aw91xxx,matrix_key_enable", &val); + if (ret) { + dev_err(aw91xxx->dev, "%s: no aw91xxx,key_enable provided, The default function is not key\n", + __func__); + } else { + AW_DEBUG("%s:aw91xxx,key_enable provided ok, val = %d\n", __func__, val); + if (val == 1) + aw91xxx->matrix_key_enable = true; + } + + aw91xxx->led_feature_enable = false; + ret = of_property_read_u32(np, "aw91xxx,led_enable", &val); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: no aw91xxx,led_enable provided, The default function is not LED\n", + __func__); + } else { + AW_DEBUG("%s:aw91xxx,led_enable provided ok, val = %d\n", __func__, val); + if (val == 1) + aw91xxx->led_feature_enable = true; + } + + aw91xxx->gpio_feature_enable = false; + ret = of_property_read_u32(np, "aw91xxx,gpio_enable", &val); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: aw91xxx,gpio_enable provided, The default function is not GPIO\n", + __func__); + } else { + AW_DEBUG("%s:aw91xxx,gpio_enable provided ok, val = %d\n", __func__, val); + if (val == 1) + aw91xxx->gpio_feature_enable = true; + } + + return 0; +} + +static int aw91xxx_hw_reset(struct aw91xxx *aw91xxx) +{ + if (aw91xxx && gpio_is_valid(aw91xxx->reset_gpio)) { + gpio_set_value_cansleep(aw91xxx->reset_gpio, 0); + usleep_range(1000, 2000); + gpio_set_value_cansleep(aw91xxx->reset_gpio, 1); + usleep_range(6000, 7000); + } else { + pr_err("%s: failed\n", __func__); + } + + return 0; +} + +static int aw91xxx_hw_off(struct aw91xxx *aw91xxx) +{ + if (aw91xxx && gpio_is_valid(aw91xxx->reset_gpio)) { + gpio_set_value_cansleep(aw91xxx->reset_gpio, 0); + usleep_range(1000, 2000); + } else { + pr_err("%s: failed\n", __func__); + } + + return 0; +} + + +/***************************************************** + * + * check chip id + * + *****************************************************/ +static int aw91xxx_read_chipid(struct aw91xxx *aw91xxx) +{ + int ret = -1; + unsigned char cnt = 0; + unsigned char reg_val = 0; + + while (cnt < AW_READ_CHIPID_RETRIES) { + ret = aw91xxx_i2c_read(aw91xxx, REG_ID, ®_val); + if (ret < 0) { + dev_err(aw91xxx->dev, + "%s: failed to read register AW91XXX_REG_ID: %d\n", __func__, ret); + } + switch (reg_val) { + case AW91XXX_ID: + pr_err("%s aw91xxx detected,chip_id=0x%02x\n", __func__, reg_val); + aw91xxx->chipid = AW91XXX_ID; + return 0; + default: + pr_err("%s unsupported device revision (0x%x)\n", __func__, reg_val); + break; + } + cnt++; + + msleep(AW_READ_CHIPID_RETRY_DELAY); + } + + return -EINVAL; +} + +static void aw91xxx_identify_vendorid(struct aw91xxx *aw91xxx) +{ + unsigned char reg_val = 0; + + aw91xxx_i2c_read(aw91xxx, REG_81H, ®_val); + if ((reg_val != 0x01) && (reg_val != 0x03)) { + pr_err("devices is 91xxB\n"); + aw91xxx->vendor_id = 0xB; + } else { + pr_err("devices is 91xxC\n"); + aw91xxx->vendor_id = 0xC; + } +} + + +/****************************************************** + * + * sys group attribute: reg + * + ******************************************************/ +static ssize_t +reg_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + + unsigned int databuf[2] = {0, 0}; + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) + aw91xxx_i2c_write(aw91xxx, (unsigned char)databuf[0], (unsigned char)databuf[1]); + + return count; +} + +static ssize_t reg_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + ssize_t len = 0; + unsigned char i = 0; + unsigned char reg_val = 0; + + for (i = 0; i < AW91XXX_REG_MAX; i++) { + if (!(aw91xxx_reg_access[i] & REG_RD_ACCESS)) + continue; + aw91xxx_i2c_read(aw91xxx, i, ®_val); + len += snprintf(buf + len, PAGE_SIZE - len, "reg:0x%02x=0x%02x\n", i, reg_val); + } + + reg_val = 0x12; + aw91xxx_i2c_read(aw91xxx, REG_81H, ®_val); + pr_err("REG_81H = 0x%x\n", reg_val); + + return len; +} + +/* 081H = 01->aw9106c,081H = 03->aw9110c */ +static void aw91xxx_led_chip_init(struct aw91xxx *aw91xxx) +{ + aw91xxx_i2c_write(aw91xxx, REG_WORK_MODE_P0, 0x00); + aw91xxx_i2c_write(aw91xxx, REG_WORK_MODE_P1, 0x00); +} + +static ssize_t +hwen_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + unsigned int databuf[1] = { 0 }; + int ret = -1; + + ret = kstrtou32(buf, 0, &databuf[0]); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: input data invalid!", __func__); + return ret; + } + + if (databuf[0] == 1) + aw91xxx_hw_reset(aw91xxx); + else + aw91xxx_hw_off(aw91xxx); + + + return count; +} + +static ssize_t hwen_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + ssize_t len = 0; + + len += + snprintf(buf + len, PAGE_SIZE - len, "hwen=%d\n", gpio_get_value(aw91xxx->reset_gpio)); + + return len; +} + +static ssize_t +blink_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) +{ + unsigned int databuf[2]; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + + if (sscanf(buf, "%x %x", &databuf[0]) == 1) + aw91xxx_led_blink(aw91xxx, databuf[0], 3); + + return len; +} + +static ssize_t +blink_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + + len += snprintf(buf + len, PAGE_SIZE - len, "aw91xx_blink()\n"); + len += snprintf(buf + len, PAGE_SIZE - len, "echo 0x9 > blink\n"); + len += snprintf(buf + len, PAGE_SIZE - len, "echo 0x12> blink\n"); + len += snprintf(buf + len, PAGE_SIZE - len, "echo 0x24> blink\n"); + + return len; +} + +static ssize_t +dim_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) +{ + unsigned int databuf[2]; + + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + + if (sscanf(buf, "%x %x", &databuf[0], &databuf[1]) == 2) { + aw91xxx_i2c_write(aw91xxx, REG_EN_BREATH, 0x0); + aw91xxx_i2c_write(aw91xxx, REG_DIM00 + databuf[0], databuf[1]); + } + + return len; +} + +static ssize_t +all_dim_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) +{ + unsigned int databuf[1] = { 0 }; + int ret = -1; + int led_num = 0; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + + ret = kstrtou32(buf, 0, &databuf[0]); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: input data invalid!", __func__); + return ret; + } + + aw91xxx_i2c_write(aw91xxx, REG_EN_BREATH, 0x0); + for (led_num = 0; led_num < AW91XXX_DIM_MAX; led_num++) + aw91xxx_i2c_write(aw91xxx, REG_DIM00 + led_num, databuf[0]); + + return len; +} + +static ssize_t +fade_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) +{ + unsigned int databuf[1] = { 0 }; + int ret = -1; + unsigned char reg_val = 0; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct aw91xxx *aw91xxx = container_of(led_cdev, struct aw91xxx, cdev); + + ret = kstrtou32(buf, 0, &databuf[0]); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: input data invalid!", __func__); + return ret; + } + + /* 1. set breath mode */ + aw91xxx_i2c_write(aw91xxx, REG_EN_BREATH, 0x3F); + + /* 2. set fade mode */ + aw91xxx_i2c_write(aw91xxx, REG_CONFIG_P0, 0x00); + aw91xxx_i2c_write(aw91xxx, REG_CONFIG_P1, 0x00); + + /* 3. set fade time (fade away << 3) | (fade in) */ + aw91xxx_i2c_write(aw91xxx, REG_FADE_TIME, + (AW91XXX_FADE_TIME_2520MS << 3) | AW91XXX_FADE_TIME_1260MS); + + /* 4. fade mode on or off */ + if (databuf[0] == 0) { + aw91xxx_i2c_read(aw91xxx, REG_OUTPUT_P0, ®_val); + aw91xxx_i2c_write(aw91xxx, REG_OUTPUT_P0, 0xFC & reg_val); + aw91xxx_i2c_read(aw91xxx, REG_OUTPUT_P1, ®_val); + aw91xxx_i2c_write(aw91xxx, REG_OUTPUT_P1, 0xF0 & reg_val); + } else { + aw91xxx_i2c_read(aw91xxx, REG_OUTPUT_P0, ®_val); + aw91xxx_i2c_write(aw91xxx, REG_OUTPUT_P0, 0x03 | reg_val); + aw91xxx_i2c_read(aw91xxx, REG_OUTPUT_P1, ®_val); + aw91xxx_i2c_write(aw91xxx, REG_OUTPUT_P1, 0x0F | reg_val); + } + + return len; +} + +static DEVICE_ATTR_RW(reg); +static DEVICE_ATTR_RW(hwen); +static DEVICE_ATTR_RW(blink); +static DEVICE_ATTR_WO(dim); +static DEVICE_ATTR_WO(all_dim); +static DEVICE_ATTR_WO(fade_mode); + + +static struct attribute *aw91xxx_attributes[] = { + &dev_attr_reg.attr, + &dev_attr_hwen.attr, + &dev_attr_blink.attr, + &dev_attr_dim.attr, + &dev_attr_all_dim.attr, + &dev_attr_fade_mode.attr, + NULL +}; + +static struct attribute_group aw91xxx_attribute_group = { + .attrs = aw91xxx_attributes +}; + + +/****************************************************** + * + * led class dev + * + ******************************************************/ +static int aw91xxx_parse_led_cdev(struct aw91xxx *aw91xxx, struct device_node *np) +{ + struct device_node *temp; + int ret = -1; + + temp = of_find_node_by_name(aw91xxx->dev->of_node, "aw91xxx,led"); + + ret = of_property_read_string(temp, "aw91xxx,name", &aw91xxx->cdev.name); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading led name, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,imax", &aw91xxx->imax); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading imax, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,brightness", + &aw91xxx->cdev.brightness); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading brightness, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,max_brightness", &aw91xxx->cdev.max_brightness); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading max brightness, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,rise_time", &aw91xxx->rise_time); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading rise_time, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,on_time", &aw91xxx->on_time); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading on_time, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,fall_time", &aw91xxx->fall_time); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading fall_time, ret = %d\n", ret); + goto free_pdata; + } + ret = of_property_read_u32(temp, "aw91xxx,off_time", &aw91xxx->off_time); + if (ret < 0) { + dev_err(aw91xxx->dev, "Failure reading off_time, ret = %d\n", ret); + goto free_pdata; + } + + INIT_DELAYED_WORK(&aw91xxx->int_work, aw91xxc_led_blink); + INIT_WORK(&aw91xxx->brightness_work, aw91xxx_brightness_work); + aw91xxx->cdev.brightness_set = aw91xxx_set_brightness; + ret = led_classdev_register(aw91xxx->dev, &aw91xxx->cdev); + if (ret) { + dev_err(aw91xxx->dev, "unable to register led ret=%d\n", ret); + goto free_pdata; + } + + ret = sysfs_create_group(&aw91xxx->cdev.dev->kobj, &aw91xxx_attribute_group); + if (ret) { + dev_err(aw91xxx->dev, "led sysfs ret: %d\n", ret); + goto free_class; + } + + aw91xxx_led_chip_init(aw91xxx); + + return 0; + +free_class: + led_classdev_unregister(&aw91xxx->cdev); +free_pdata: + return ret; +} + + +/****************************************************** + * + * aw91xxx gpio + * + ******************************************************/ +static int aw91xxx_parse_for_single_gpio(struct device_node *gpio_node, + struct aw91xxx *aw91xxx, + struct aw91xxx_gpio *p_gpio_data) +{ + int ret = 0; + struct device_node *temp = NULL; + int i = 0; + struct aw91xxx_singel_gpio *p_single_gpio_data = p_gpio_data->single_gpio_data; + + for_each_child_of_node(gpio_node, temp) { + ret = of_property_read_u32(temp, "aw91xxx,gpio_idx", + &p_single_gpio_data[i].gpio_idx); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: no aw91xxx,gpio_idx, abort\n", __func__); + goto err_id; + } + ret = of_property_read_u32(temp, "aw91xxx,gpio_dir", + &p_single_gpio_data[i].gpio_direction); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s, no aw91xxx,gpio_dir\n", __func__); + goto err_id; + } + ret = of_property_read_u32(temp, "aw91xxx,gpio_default_val", + &p_single_gpio_data[i].state); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s, no aw91xxx,gpio_default_val, abort\n", __func__); + goto err_id; + } + + p_gpio_data->gpio_mask |= 0x01 << p_single_gpio_data[i].gpio_idx; + AW_DEBUG("idx = %d, aw91xxx,gpio_idx = %d\n", i, p_single_gpio_data[i].gpio_idx); + i++; + } + + AW_DEBUG("%s gpio_mask = 0x%x\r\n", __func__, p_gpio_data->gpio_mask); + + return 0; + +err_id: + return ret; +} + +/* val 0 -> led, gpio -> 1 */ +static void +aw91xxx_set_port_mode_by_mask(struct aw91xxx *aw91xxx, unsigned int mask, unsigned int val) +{ + unsigned char reg_val[2] = {0}; + int i = 0; + + /* p0_0 - P1_7 */ + aw91xxx_i2c_read_bits(aw91xxx, REG_WORK_MODE_P0, ®_val[0], ARRAY_SIZE(reg_val)); + if (aw91xxx->gpio_feature_enable) { + for (i = 0; i < AW91XXX_PORT_NUM; i++) { + if (mask & (0x01 << i)) { + if (val) { + if (i < 4) + reg_val[1] |= 0x1 << i; + else + reg_val[0] |= 0x1 << (i - 4); + } else { + if (i < 4) + reg_val[1] &= ~(0x1 << i); + else + reg_val[0] &= ~(0x1 << (i - 4)); + } + } + } + } else { + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (mask & (0x01 << i)) { + if (val) { + if (i < 8) + reg_val[0] |= 0x1 << i; + else + reg_val[1] |= 0x1<<(i - 8); + } else { + if (i < 8) + reg_val[0] &= ~(0x1 << i); + else + reg_val[1] &= ~(0x1 << (i - 8)); + } + } + } + } + + aw91xxx_i2c_write_bits(aw91xxx, REG_WORK_MODE_P0, reg_val, ARRAY_SIZE(reg_val)); +} + +static void aw91xxx_set_port_output_mode(struct aw91xxx *aw91xxx, int val) +{ + unsigned char reg_val[1] = {0}; + + aw91xxx_i2c_read(aw91xxx, REG_CTRL, ®_val[0]); /* p0_0 -- p0_7 */ + if (val) + reg_val[0] |= 0x01 << 4; + else + reg_val[0] &= ~(0x01 << 4); + aw91xxx_i2c_write(aw91xxx, REG_CTRL, reg_val[0]); +} + +/* val 1-> output 0->input */ +static void aw91xxx_set_port_direction_by_mask(struct aw91xxx *aw91xxx, unsigned int mask, int val) +{ + unsigned char reg_val[2] = {0}; + int i = 0; + + aw91xxx_i2c_read_bits(aw91xxx, REG_CONFIG_P0, reg_val, ARRAY_SIZE(reg_val)); + /* 0-output 1-input */ + if (aw91xxx->gpio_feature_enable) { + for (i = 0; i < AW91XXX_PORT_NUM; i++) { + if (mask & (0x01 << i)) { + if (!val) { + if (i < 4) + reg_val[1] |= 0x1 << i; + else + reg_val[0] |= 0x1<<(i - 4); + } else { + if (i < 4) + reg_val[1] &= ~(0x1 << i); + else + reg_val[0] &= ~(0x1 << (i - 4)); + } + } + } + } else { + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (mask & (0x01 << i)) { + if (!val) { + if (i < 8) + reg_val[0] |= 0x1 << i; + else + reg_val[1] |= 0x1<<(i - 8); + } else { + if (i < 8) + reg_val[0] &= ~(0x1 << i); + else + reg_val[1] &= ~(0x1 << (i - 8)); + } + } + } + } + + if (val == 0) + aw91xxx_set_port_output_mode(aw91xxx, 1); + + aw91xxx_i2c_write_bits(aw91xxx, REG_CONFIG_P0, reg_val, ARRAY_SIZE(reg_val)); +} + +/* val 1-> output high 0->output low */ +static void aw91xxx_set_port_output_state_by_mask(struct aw91xxx *aw91xxx, +unsigned int mask, int val) +{ + unsigned char reg_val[2] = {0}; + int i = 0; + + aw91xxx_i2c_read_bits(aw91xxx, REG_OUTPUT_P0, reg_val, ARRAY_SIZE(reg_val)); + if (aw91xxx->gpio_feature_enable) { + for (i = 0; i < AW91XXX_PORT_NUM; i++) { + if (mask & (0x01 << i)) { + if (val) { + if (i < 4) + reg_val[1] |= 0x1 << i; + else + reg_val[0] |= 0x1 << (i - 4); + } else { + if (i < 4) + reg_val[1] &= ~(0x1 << i); + else + reg_val[0] &= ~(0x1 << (i - 4)); + } + } + } + } else { + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (mask & (0x01 << i)) { + if (val) { + if (i < 8) + reg_val[0] |= 0x1 << i; + else + reg_val[1] |= 0x1 << (i - 8); + } else { + if (i < 8) + reg_val[0] &= ~(0x1 << i); + else + reg_val[1] &= ~(0x1 << (i - 8)); + } + } + } + } + + aw91xxx_i2c_write_bits(aw91xxx, REG_OUTPUT_P0, reg_val, ARRAY_SIZE(reg_val)); +} + +/* val 1 -> enable 0 - > disable */ +static void +aw91xxx_enbale_interrupt_by_mask(struct aw91xxx *p_aw91xxx, unsigned int mask, unsigned int val) +{ + unsigned char reg_val[2] = {0}; + int i = 0; + + aw91xxx_i2c_read_bits(p_aw91xxx, REG_INT_P0, reg_val, ARRAY_SIZE(reg_val)); + /* 0 enable 1 disable */ + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (mask & (0x01<single_gpio_data; + + aw91xxx_set_port_mode_by_mask(aw91xxx, + p_gpio_data->gpio_mask, 1); /* gpio mode */ + aw91xxx_set_port_output_mode(aw91xxx, + p_gpio_data->output_mode); /* OD or pull push mode */ + for (i = 0; i < p_gpio_data->gpio_num; i++) { + aw91xxx_set_port_direction_by_mask(aw91xxx, 0x01 << p_single_gpio_data[i].gpio_idx, + p_single_gpio_data[i].gpio_direction); + if (p_single_gpio_data[i].gpio_direction) { /* output */ + aw91xxx_set_port_output_state_by_mask(aw91xxx, + 0x01 << p_single_gpio_data[i].gpio_idx, + p_single_gpio_data[i].state); + } + } + +} + +/* echo gpio_idx dirction state > aw91xxx_gpio */ +static ssize_t awgpio_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t len) +{ + unsigned int databuf[3]; + int i = 0; + struct aw91xxx *aw91xxx = dev_get_drvdata(dev); + struct aw91xxx_gpio *p_gpio_data = aw91xxx->gpio_data; + struct aw91xxx_singel_gpio *p_single_gpio_data = p_gpio_data->single_gpio_data; + + if (sscanf(buf, "%x %x %x", &databuf[0], &databuf[1], &databuf[2]) == 3) + AW_DEBUG("aw91xxx gpio cmd param: %x %x %x\n", databuf[0], databuf[1], databuf[2]); + else + return len; + + if (p_gpio_data->gpio_mask & (0x01 << databuf[0])) { + for (i = 0; i < p_gpio_data->gpio_num; i++) { + if (p_single_gpio_data[i].gpio_idx == databuf[0]) { + if (p_single_gpio_data[i].gpio_direction != databuf[1]) { + p_single_gpio_data[i].gpio_direction = databuf[1]; + aw91xxx_set_port_direction_by_mask(aw91xxx, 0x01 << p_single_gpio_data[i].gpio_idx, + p_single_gpio_data[i].gpio_direction); + } + if (p_single_gpio_data[i].gpio_direction == 0x01) { /* output */ + if (p_single_gpio_data[i].state != databuf[2]) { + p_single_gpio_data[i].state = databuf[2]; + aw91xxx_set_port_output_state_by_mask(aw91xxx, + 0x01 << p_single_gpio_data[i].gpio_idx, + p_single_gpio_data[i].state); + } + } + } + } + } + + return len; +} + +static ssize_t awgpio_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + int i = 0; + struct aw91xxx *aw91xxx = dev_get_drvdata(dev); + struct aw91xxx_singel_gpio *p_single_gpio_data = aw91xxx->gpio_data->single_gpio_data; + + len += snprintf(buf + len, PAGE_SIZE - len, "Uasge: echo gpio_idx dirction state > aw91xxx_gpio\n"); + for (i = 0; i < aw91xxx->gpio_data->gpio_num; i++) { + len += snprintf(buf + len, PAGE_SIZE - len, "aw91xxx gpio idx = %x, dir = %x, state = %x\n", + p_single_gpio_data[i].gpio_idx, + p_single_gpio_data[i].gpio_direction, + p_single_gpio_data[i].state); + } + + return len; +} + +static DEVICE_ATTR_RW(awgpio); +static struct attribute *aw91xxx_gpio_attributes[] = { + &dev_attr_awgpio.attr, + NULL +}; + +static struct attribute_group aw91xxx_gpio_attribute_group = { + .attrs = aw91xxx_gpio_attributes +}; + +static int aw91xxx_gpio_feature_init(struct aw91xxx *aw91xxx) +{ + int ret = 0; + int i = 0; + struct device_node *gpio_node = NULL; + struct aw91xxx_gpio *p_gpio_data = NULL; + int gpio_num = 0; + + p_gpio_data = devm_kzalloc(aw91xxx->dev, sizeof(struct aw91xxx_gpio), GFP_KERNEL); + if (p_gpio_data == NULL) + return -ENOMEM; + + aw91xxx->gpio_data = p_gpio_data; + + gpio_node = of_find_node_by_name(aw91xxx->dev->of_node, "aw91xxx,gpio"); + if (gpio_node == NULL) { + dev_err(aw91xxx->dev, "%s: can't find aw91xxx,gpio return failed\r\n", __func__); + ret = -1; + goto err_id; + } + ret = of_property_read_u32(gpio_node, "aw91xxx,gpio_mode", &p_gpio_data->output_mode); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s: no aw91xxx,gpio_mode,abort\n", __func__); + goto err_id; + } + + gpio_num = of_get_child_count(gpio_node); + p_gpio_data->gpio_num = gpio_num; + p_gpio_data->single_gpio_data = devm_kzalloc(aw91xxx->dev, + sizeof(struct aw91xxx_singel_gpio) * gpio_num, + GFP_KERNEL); + + if (p_gpio_data->single_gpio_data == NULL) { + dev_err(aw91xxx->dev, "%s: malloc memory failed\r\n", __func__); + ret = -ENOMEM; + goto err_id; + } + ret = aw91xxx_parse_for_single_gpio(gpio_node, aw91xxx, p_gpio_data); + if (ret) { + dev_err(aw91xxx->dev, "aw91xxx_parse_single_gpio failed\r\n"); + goto free_mem; + } + + for (i = 0; i < gpio_num; i++) + p_gpio_data->single_gpio_data[i].priv = aw91xxx; + ret = sysfs_create_group(&aw91xxx->dev->kobj, &aw91xxx_gpio_attribute_group); + if (ret) { + dev_err(aw91xxx->dev, "gpio sysfs failed ret: %d\n", ret); + goto free_mem; + } + + aw91xxx_gpio_chip_init(aw91xxx, p_gpio_data); + + return 0; + +free_mem: + devm_kfree(aw91xxx->dev, p_gpio_data->single_gpio_data); + devm_kfree(aw91xxx->dev, aw91xxx->gpio_data); +err_id: + return ret; +} + +static void aw91xxx_gpio_free_all_resource(struct aw91xxx *aw91xxx) +{ + if (aw91xxx->gpio_feature_enable) { + devm_kfree(aw91xxx->dev, aw91xxx->gpio_data->single_gpio_data); + devm_kfree(aw91xxx->dev, aw91xxx->gpio_data); + } +} + +/********************************************************* + * + * aw91xxx key feature + * + ********************************************************/ +static int aw91xxx_parse_dt_for_key(struct aw91xxx_key *p_key_data, struct device_node *np) +{ + int ret = 0; + int i = 0; + unsigned int val = 0; + struct aw91xxx *aw91xxx = p_key_data->priv; + + p_key_data->wake_up_enable = false; + ret = of_property_read_u32(np, "aw91xxx,wake_up_enable", &val); + if (ret == 0) { + if (val == 1) + p_key_data->wake_up_enable = true; + } else { + dev_err(aw91xxx->dev, "%s: no aw91xxx,wake_up_enable, abort\n", __func__); + return ret; + } + + ret = of_property_read_u32(np, "aw91xxx,input_port_mask", &p_key_data->input_port_mask); + if (ret) { + dev_err(aw91xxx->dev, "%s: no aw91xxx,input_port_mask, abort\n", __func__); + return ret; + } + + p_key_data->input_port_nums = 0; + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (p_key_data->input_port_mask & (0x01 << i)) + p_key_data->input_port_nums++; + } + + if (aw91xxx->single_key_enable) { + p_key_data->key_mask = p_key_data->input_port_mask; + AW_DEBUG("aw91xxx key input_port_mask = 0x%x, input_num = %d\n", + p_key_data->input_port_mask, + p_key_data->input_port_nums); + } + + if (aw91xxx->matrix_key_enable) { + ret = of_property_read_u32(np, "aw91xxx,output_port_mask", + &p_key_data->output_port_mask); + if (ret) { + dev_err(aw91xxx->dev, "%s, no aw91xxx,output_port_mask, abort\n", __func__); + return ret; + } + p_key_data->output_port_nums = 0; + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (p_key_data->output_port_mask & (0x01 << i)) + p_key_data->output_port_nums++; + } + p_key_data->key_mask + = p_key_data->input_port_mask | p_key_data->output_port_mask; + AW_DEBUG("aw91xxx key output_port_mask = 0x%x, output_nmu = %d\n", + p_key_data->output_port_mask, + p_key_data->output_port_nums); + } + + return 0; +} + +irqreturn_t aw91xxx_irq_func(int irq, void *key_data) +{ + struct aw91xxx_key *p_key_data = (struct aw91xxx_key *)key_data; + struct aw91xxx *p_aw91xxx = p_key_data->priv; + + disable_irq_nosync(p_key_data->priv->irq_num); + /* disable aw91xxx input interrupt */ + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 0); + aw91xxx_clear_interrupt(p_aw91xxx); + schedule_delayed_work(&p_key_data->int_work, msecs_to_jiffies(p_key_data->debounce_delay)); + return 0; +} + +static int aw91xxx_irq_register(struct aw91xxx *p_aw91xxx) +{ + int ret = 0; + struct device_node *np = p_aw91xxx->dev->of_node; + + p_aw91xxx->irq_gpio = of_get_named_gpio(np, "irq-gpio", 0); + if (p_aw91xxx->irq_gpio < 0) { + dev_err(p_aw91xxx->dev, "%s: get irq gpio failed\r\n", __func__); + return -EINVAL; + } + ret = devm_gpio_request_one(p_aw91xxx->dev, p_aw91xxx->irq_gpio, GPIOF_OUT_INIT_LOW, "aw91xxx irq gpio"); + if (ret) { + dev_err(p_aw91xxx->dev, "%s: devm_gpio_request irq gpio failed\r\n", __func__); + return -EBUSY; + } + gpio_direction_input(p_aw91xxx->irq_gpio); + p_aw91xxx->irq_num = gpio_to_irq(p_aw91xxx->irq_gpio); + if (p_aw91xxx->irq_num < 0) { + ret = p_aw91xxx->irq_num; + dev_err(p_aw91xxx->dev, "%s gpio to irq failed\r\n", __func__); + goto err; + } + + AW_DEBUG("aw91xxx irq num=%d\n", p_aw91xxx->irq_num); + ret = devm_request_threaded_irq(p_aw91xxx->dev, p_aw91xxx->irq_num, NULL, + aw91xxx_irq_func, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "aw91xxx_irq", p_aw91xxx->key_data); + if (ret < 0) { + dev_err(p_aw91xxx->dev, "%s register irq failed\r\n", __func__); + goto err; + } + /* enable_irq_wake(p_aw91xxx->irq_num); */ + device_init_wakeup(p_aw91xxx->dev, 1); + return 0; +err: + gpio_free(p_aw91xxx->irq_gpio); + return ret; +} + +static int aw91xxx_input_dev_register(struct aw91xxx *p_aw91xxx) +{ + int ret = 0; + int i = 0; + struct aw91xxx_key *p_key_data = p_aw91xxx->key_data; + + p_key_data->input = input_allocate_device(); + + if (p_key_data->input == NULL) { + ret = -ENOMEM; + dev_err(p_aw91xxx->dev, "%s: failed to allocate input device\n", __func__); + return ret; + } + p_key_data->input->name = "aw91xxx-key"; + p_key_data->input->dev.parent = p_aw91xxx->dev; + p_key_data->keymap_len = sizeof(key_map) / sizeof(KEY_STATE); + p_key_data->keymap = (KEY_STATE *)&key_map; + input_set_drvdata(p_key_data->input, p_key_data); + + __set_bit(EV_KEY, p_key_data->input->evbit); + __set_bit(EV_SYN, p_key_data->input->evbit); + for (i = 0; i < p_key_data->keymap_len; i++) + __set_bit(p_key_data->keymap[i].key_code & KEY_MAX, p_key_data->input->keybit); + + ret = input_register_device(p_key_data->input); + if (ret) { + input_free_device(p_key_data->input); + dev_err(p_aw91xxx->dev, "%s: failed to allocate input device\n", __func__); + return ret; + } + return 0; +} + +void aw91xxx_int_work(struct work_struct *work) +{ + struct delayed_work *p_delayed_work = container_of(work, + struct delayed_work, + work); + struct aw91xxx_key *p_key_data = container_of(p_delayed_work, + struct aw91xxx_key, + int_work); + schedule_work(&p_key_data->key_work); +} + +/* val 1-> output high 0->output low */ +static unsigned int aw91xxx_get_port_input_state(struct aw91xxx *p_aw91xxx) +{ + unsigned char reg_val[2] = {0}; + + aw91xxx_i2c_read(p_aw91xxx, REG_INPUT_P0, ®_val[0]); + aw91xxx_i2c_read(p_aw91xxx, REG_INPUT_P1, ®_val[1]); + return reg_val[0] | (reg_val[1] << 8); +} + +static void aw91xxx_key_work(struct work_struct *work) +{ + int i = 0; + int j = 0; + int real_idx = 0; + int real_row = 0; + int real_col = 0; + int key_code = 0; + int key_val = 0; + int key_num = 0; + unsigned int input_val = 0; + unsigned int retry_state; + struct aw91xxx_key *p_key_data = container_of(work, struct aw91xxx_key, key_work); + struct aw91xxx *p_aw91xxx = p_key_data->priv; + unsigned int *new_state = p_key_data->new_output_state; + unsigned int *old_state = p_key_data->old_output_state; + unsigned char reg_val[2] = {0}; + + //if wake_up_enable set to 1,the key fuction can be use in suspend mode. + //if wake_up_enable set to 0,the key fuction will be banned in suspend mode. + + if ((!p_key_data->wake_up_enable) && (!p_aw91xxx->screen_state)) { + enable_irq(p_aw91xxx->irq_num); + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 1); + aw91xxx_clear_interrupt(p_aw91xxx); + return; + } + + if (p_aw91xxx->matrix_key_enable) { + for (i = 0, real_idx = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (p_key_data->output_port_mask & (0x01 << i)) { + aw91xxx_i2c_read_bits(p_aw91xxx, REG_CONFIG_P0, + reg_val, + ARRAY_SIZE(reg_val)); + + input_val = reg_val[0] | (reg_val[1] << 8); + input_val |= p_key_data->output_port_mask; + input_val &= ~(0x01 << i); + + reg_val[0] = input_val & 0xff; + reg_val[1] = (input_val >> 8) & 0xff; + + aw91xxx_i2c_write_bits(p_aw91xxx, REG_CONFIG_P0, + reg_val, ARRAY_SIZE(reg_val)); + + aw91xxx_i2c_read_bits(p_aw91xxx, REG_INPUT_P0, + reg_val, ARRAY_SIZE(reg_val)); + new_state[real_idx] = (reg_val[0] | (reg_val[1] << 8)) & p_key_data->input_port_mask; + real_idx++; + } + } + /* key state change */ + if (memcmp(&new_state[0], &old_state[0], p_key_data->output_port_nums * sizeof(unsigned int))) { + /* stage changed */ + for (i = 0, real_col = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (p_key_data->output_port_mask & (0x01 << i)) { + if (new_state[real_col] != old_state[real_col]) { + for (j = 0, real_row = 0; j < AW91XXX_KEY_PORT_MAX; j++) { + if (p_key_data->input_port_mask & (0x01 << j)) { + if ((new_state[real_col] & (0x01 << j)) != (old_state[real_col] & (0x01 << j))) { + key_code = p_key_data->keymap[real_row * p_key_data->output_port_nums + real_col].key_code; + key_val = (old_state[real_col] & (0x01 << j)) ? 1 : 0;/* press or release */ + AW_DEBUG("aw91xxx report: key_num = %d\n", (real_row * p_key_data->output_port_nums + real_col)); + AW_DEBUG("aw91xxx report: key_code = %x, key_val = %d\n", key_code, key_val); + AW_DEBUG("aw91xxx real_row = %d, real_col = %d\n", real_row, real_col); + input_report_key(p_key_data->input, key_code, key_val); + input_sync(p_key_data->input); + } + real_row++; + } + } + } + real_col++; + } + } + } + + memcpy(&old_state[0], &new_state[0], p_key_data->output_port_nums * sizeof(unsigned int)); + + /* all key release */ + if (!memcmp(&new_state[0], &p_key_data->def_output_state[0], + p_key_data->output_port_nums * sizeof(unsigned int))) { + aw91xxx_set_port_direction_by_mask(p_aw91xxx, p_key_data->output_port_mask, 1); /* set output mode */ + aw91xxx_clear_interrupt(p_aw91xxx); /* clear inputerrupt */ + enable_irq(p_aw91xxx->irq_num); + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 1);/* enable input interrupt */ + retry_state = aw91xxx_get_port_input_state(p_aw91xxx); + if ((retry_state & p_key_data->input_port_mask) != p_key_data->input_port_mask) { + disable_irq_nosync(p_aw91xxx->irq_num); + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 0); + } else { + AW_DEBUG("%s enter, all key release.\n", __func__); + return; + } + } + hrtimer_start(&p_key_data->key_timer, ktime_set(0, (1000 / HRTIMER_FRAME) * 1000), HRTIMER_MODE_REL); + } + + if (p_aw91xxx->single_key_enable) { + p_key_data->new_input_state = aw91xxx_get_port_input_state(p_aw91xxx); + p_key_data->new_input_state &= p_key_data->input_port_mask; + real_idx = 0; + if (p_key_data->new_input_state != p_key_data->old_input_state) { + for (i = 0; i < AW91XXX_KEY_PORT_MAX; i++) { + if (p_key_data->input_port_mask & (0x01 << i)) { + if ((p_key_data->new_input_state & (0x01 << i)) != (p_key_data->old_input_state & (0x01 << i))) { + key_code = p_key_data->keymap[real_idx].key_code; + key_val = (p_key_data->old_input_state & 0x01 << i) ? 1 : 0; /* press or release */ + key_num = aw91xxx_separate_key_data[real_idx]; + /*if (key_val == 1) */ + /* AW_DEBUG("%s, key%d pressed\n", __func__, key_num);*/ + /*else*/ + /* AW_DEBUG("%s, key%d release\n", __func__, key_num);*/ + /* AW_DEBUG("%s, key_code = 0x%x, key_val = 0x%x\r\n", __func__, key_code, key_val); */ + input_report_key(p_key_data->input, key_code, key_val); + input_sync(p_key_data->input); + } + real_idx++; + } + } + } + /* AW_DEBUG("%s,new input state = 0x%x, old input state = 0x%x\r\n", */ + /* __func__, p_key_data->new_input_state, */ + /* p_key_data->old_input_state); */ + + p_key_data->old_input_state = p_key_data->new_input_state; + aw91xxx_clear_interrupt(p_aw91xxx); + enable_irq(p_aw91xxx->irq_num); + /* enable input interrupt */ + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 1); + return; + } +} + +static enum hrtimer_restart aw91xxx_timer_func(struct hrtimer *p_hrtimer) +{ + struct aw91xxx_key *p_key_data = container_of(p_hrtimer, struct aw91xxx_key, key_timer); + + schedule_work(&p_key_data->key_work); + return HRTIMER_NORESTART; +} + +static void aw91xxx_key_chip_init(struct aw91xxx_key *p_key_data) +{ + unsigned int all_mask = 0; + struct aw91xxx *p_aw91xxx = p_key_data->priv; + + + + disable_irq_nosync(p_key_data->priv->irq_num); + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, AW91XXX_INT_MASK, 0); /* disale P0 P1 interrupt */ + all_mask = p_key_data->input_port_mask | p_key_data->output_port_mask; + aw91xxx_set_port_mode_by_mask(p_aw91xxx, all_mask, 1); /* set gpio mode */ + aw91xxx_set_port_direction_by_mask(p_aw91xxx, p_key_data->input_port_mask, 0); /* input mode */ + aw91xxx_set_port_direction_by_mask(p_aw91xxx, p_key_data->output_port_mask, 1); /* output mode */ + aw91xxx_set_port_output_mode(p_aw91xxx, 1); /* set output port pull push mode */ + + aw91xxx_set_port_output_state_by_mask(p_aw91xxx, p_key_data->output_port_mask, 0);/* set output low */ + + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 1); /* enable input interrupt */ + /* clear inputerrupt */ + aw91xxx_clear_interrupt(p_aw91xxx); + enable_irq(p_key_data->priv->irq_num); +} + +static void aw91xxx_key_free_all_resource(struct aw91xxx *aw91xxx) +{ + if (aw91xxx->matrix_key_enable) { + devm_kfree(aw91xxx->dev, aw91xxx->key_data->new_output_state); + devm_kfree(aw91xxx->dev, aw91xxx->key_data->old_output_state); + devm_kfree(aw91xxx->dev, aw91xxx->key_data->def_output_state); + input_unregister_device(aw91xxx->key_data->input); + input_free_device(aw91xxx->key_data->input); + gpio_free(aw91xxx->irq_gpio); + devm_kfree(aw91xxx->dev, aw91xxx->key_data); + } +} + +static void aw91xxx_single_key_chip_init(struct aw91xxx_key *p_key_data) +{ + unsigned int all_mask = 0; + struct aw91xxx *p_aw91xxx = p_key_data->priv; + + disable_irq_nosync(p_key_data->priv->irq_num); + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, AW91XXX_INT_MASK, 0); /* disale P0 P1 interrupt */ + all_mask = p_key_data->input_port_mask; + p_key_data->old_input_state = p_key_data->input_port_mask; + aw91xxx_set_port_mode_by_mask(p_aw91xxx, all_mask, 1); /* set gpio mode */ + aw91xxx_set_port_direction_by_mask(p_aw91xxx, p_key_data->input_port_mask, 0); /* input mode */ + aw91xxx_set_port_output_mode(p_aw91xxx, 1); /* set output port pull push mode */ + + aw91xxx_enbale_interrupt_by_mask(p_aw91xxx, p_key_data->input_port_mask, 1);/* enable input interrupt */ + /* clear inputerrupt */ + aw91xxx_clear_interrupt(p_aw91xxx); + enable_irq(p_key_data->priv->irq_num); +} + +static int aw91xxx_key_feature_init(struct aw91xxx *aw91xxx) +{ + int ret = 0; + int i = 0; + struct aw91xxx_key *p_aw91xxx = NULL; + struct device_node *key_node = NULL; + + p_aw91xxx = devm_kzalloc(aw91xxx->dev, sizeof(struct aw91xxx_key), GFP_KERNEL); + if (p_aw91xxx == NULL) + return -ENOMEM; + + aw91xxx->key_data = p_aw91xxx; + p_aw91xxx->priv = aw91xxx; + key_node = of_find_node_by_name(aw91xxx->dev->of_node, "aw91xxx,key"); + if (key_node == NULL) { + dev_err(aw91xxx->dev, "%s: can't find aw91xxx,key node return failed\n", __func__); + ret = -EINVAL; + goto err_id; + } + ret = aw91xxx_parse_dt_for_key(p_aw91xxx, key_node); + if (ret) { + dev_err(aw91xxx->dev, "aw91xxx_parse_dt_for_key failed, check dts\n"); + goto err_id; + } + p_aw91xxx->old_output_state = devm_kzalloc(aw91xxx->dev, + sizeof(unsigned int) * p_aw91xxx->output_port_nums, + GFP_KERNEL); + if (p_aw91xxx->old_output_state == NULL) { + dev_err(aw91xxx->dev, "%s:aw91xxx->old_output_state malloc memory failed\r\n", __func__); + goto err_id; + } + p_aw91xxx->new_output_state = devm_kzalloc(aw91xxx->dev, + sizeof(unsigned int) * p_aw91xxx->output_port_nums, + GFP_KERNEL); + if (p_aw91xxx->new_output_state == NULL) { + dev_err(aw91xxx->dev, "%s:aw91xxx->new_output_state malloc memory failed\r\n", __func__); + goto free_old_output; + } + + p_aw91xxx->def_output_state = devm_kzalloc(aw91xxx->dev, + sizeof(unsigned int) * p_aw91xxx->output_port_nums, GFP_KERNEL); + if (p_aw91xxx->def_output_state == NULL) { + ret = -ENOMEM; + goto free_new_output; + } + + for (i = 0; i < p_aw91xxx->output_port_nums; i++) { + p_aw91xxx->new_output_state[i] = p_aw91xxx->input_port_mask; + p_aw91xxx->old_output_state[i] = p_aw91xxx->input_port_mask; + p_aw91xxx->def_output_state[i] = p_aw91xxx->input_port_mask; + } + + ret = aw91xxx_input_dev_register(aw91xxx); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s input dev register failed\r\n", __func__); + goto free_def_output; + } + + p_aw91xxx->debounce_delay = 1; + INIT_DELAYED_WORK(&p_aw91xxx->int_work, aw91xxx_int_work); + INIT_WORK(&p_aw91xxx->key_work, aw91xxx_key_work); + hrtimer_setup(&p_aw91xxx->key_timer, aw91xxx_timer_func, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ret = aw91xxx_irq_register(aw91xxx); + if (ret < 0) { + dev_err(aw91xxx->dev, "%s irq register failed\r\n", __func__); + goto input_dev; + } + + if (aw91xxx->matrix_key_enable) + aw91xxx_key_chip_init(p_aw91xxx); + + if (aw91xxx->single_key_enable) + aw91xxx_single_key_chip_init(p_aw91xxx); + return 0; +input_dev: + input_unregister_device(p_aw91xxx->input); +free_def_output: + devm_kfree(aw91xxx->dev, p_aw91xxx->def_output_state); +free_new_output: + devm_kfree(aw91xxx->dev, p_aw91xxx->new_output_state); +free_old_output: + devm_kfree(aw91xxx->dev, p_aw91xxx->old_output_state); +err_id: + devm_kfree(aw91xxx->dev, aw91xxx->key_data); + return ret; +} + +/****************************************************** + * + * i2c driver + * + ******************************************************/ +static int aw91xxx_i2c_probe(struct i2c_client *i2c) +{ + struct aw91xxx *aw91xxx; + struct device_node *np = i2c->dev.of_node; + int ret; + + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) { + dev_err(&i2c->dev, "check_functionality failed\n"); + return -EIO; + } + + aw91xxx = devm_kzalloc(&i2c->dev, sizeof(struct aw91xxx), GFP_KERNEL); + if (aw91xxx == NULL) + return -ENOMEM; + + aw91xxx->dev = &i2c->dev; + aw91xxx->i2c = i2c; + + i2c_set_clientdata(i2c, aw91xxx); + + /* aw91xxx rst & int */ + if (np) { + ret = aw91xxx_parse_dt(&i2c->dev, aw91xxx, np); + if (ret) { + dev_err(&i2c->dev, "%s: failed to parse device tree node\n", __func__); + goto err; + } + } else { + aw91xxx->reset_gpio = -1; + } + + if (gpio_is_valid(aw91xxx->reset_gpio)) { + ret = devm_gpio_request_one(&i2c->dev, aw91xxx->reset_gpio, + GPIOF_OUT_INIT_LOW, "aw91xxx_rst"); + if (ret) { + dev_err(&i2c->dev, "%s: rst request failed\n", __func__); + goto err; + } + } + + /* hardware reset */ + aw91xxx_hw_reset(aw91xxx); + + /* aw91xxx chip id */ + ret = aw91xxx_read_chipid(aw91xxx); + if (ret < 0) { + dev_err(&i2c->dev, "%s: aw91xxx_read_chipid failed ret=%d\n", __func__, ret); + goto err_free_rst; + } + + aw91xxx_identify_vendorid(aw91xxx); + + dev_set_drvdata(&i2c->dev, aw91xxx); + + if (aw91xxx->single_key_enable) { + /* single key init */ + ret = aw91xxx_key_feature_init(aw91xxx); + if (ret) { + dev_err(aw91xxx->dev, "aw91xxx single key feature init failed \r\n"); + goto err_free_rst; + } + } + + if (aw91xxx->matrix_key_enable) { + /* key init */ + ret = aw91xxx_key_feature_init(aw91xxx); + if (ret) { + dev_err(aw91xxx->dev, "aw91xxx key feature init failed \r\n"); + goto err_free_rst; + } + } + + if (aw91xxx->led_feature_enable) { + ret = aw91xxx_parse_led_cdev(aw91xxx, np); + if (ret < 0) { + dev_err(&i2c->dev, "%s error creating led class dev\n", __func__); + goto free_key; + } + } + + if (aw91xxx->gpio_feature_enable) { + /* gpio init */ + ret = aw91xxx_gpio_feature_init(aw91xxx); + if (ret) { + dev_err(aw91xxx->dev, "aw91xxx gpio feature init failed \r\n"); + goto free_key; + } + } + aw91xxx->screen_state = true; + + pr_err("%s probe completed successfully!\n", __func__); + + return 0; + +free_key: + aw91xxx_key_free_all_resource(aw91xxx); +err_free_rst: + gpio_free(aw91xxx->reset_gpio); +err: + devm_kfree(&i2c->dev, aw91xxx); + return ret; +} + +static void aw91xxx_i2c_remove(struct i2c_client *i2c) +{ + struct aw91xxx *aw91xxx = i2c_get_clientdata(i2c); + + if (gpio_is_valid(aw91xxx->reset_gpio)) + gpio_free(aw91xxx->reset_gpio); + aw91xxx_gpio_free_all_resource(aw91xxx); + devm_kfree(aw91xxx->dev, aw91xxx); + +} + +static const struct i2c_device_id aw91xxx_i2c_id[] = { + { AW91XXX_I2C_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, aw91xxx_i2c_id); + +static const struct of_device_id aw91xxx_dt_match[] = { + { .compatible = "awinic,aw91xxx_led" }, + { }, +}; + +static struct i2c_driver aw91xxx_i2c_driver = { + .driver = { + .name = AW91XXX_I2C_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(aw91xxx_dt_match), + }, + .probe = aw91xxx_i2c_probe, + .remove = aw91xxx_i2c_remove, + .id_table = aw91xxx_i2c_id, +}; + +static int __init aw91xxx_i2c_init(void) +{ + int ret = 0; + + pr_err("aw91xxx driver version %s\n", AW91XXX_DRIVER_VERSION); + + ret = i2c_add_driver(&aw91xxx_i2c_driver); + if (ret) { + pr_err("fail to add aw91xxx device into i2c\n"); + return ret; + } + + return 0; +} +module_init(aw91xxx_i2c_init); + +static void __exit aw91xxx_i2c_exit(void) +{ + i2c_del_driver(&aw91xxx_i2c_driver); +} +module_exit(aw91xxx_i2c_exit); + + +MODULE_DESCRIPTION("AW91XXX LED Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-aw91xxx.h b/drivers/leds/leds-aw91xxx.h new file mode 100644 index 000000000000..d69c2334ffe0 --- /dev/null +++ b/drivers/leds/leds-aw91xxx.h @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _AW91XXX_H_ +#define _AW91XXX_H_ + +#define AWINIC_DEBUG 1 + +#ifdef AWINIC_DEBUG +#define AW_DEBUG(fmt, args...) pr_info(fmt, ##args) +#else +#define AW_DEBUG(fmt, ...) + +#endif + +#define MAX_I2C_BUFFER_SIZE 65536 + +#define AW91XXX_ID 0x23 +#define AW91XXX_KEY_PORT_MAX (0x10) /* 16 */ +#define AW91XXX_INT_MASK (0xFFFF) + +enum AW91XXX_FADE_TIME { + AW91XXX_FADE_TIME_0000MS = 0x00, + AW91XXX_FADE_TIME_0315MS = 0X01, + AW91XXX_FADE_TIME_0630MS = 0x02, + AW91XXX_FADE_TIME_1260MS = 0x03, + AW91XXX_FADE_TIME_2520MS = 0x04, + AW91XXX_FADE_TIME_5040MS = 0x05 +}; + +enum aw91xxx_gpio_dir { + AW91XXX_GPIO_INPUT = 0, + AW91XXX_GPIO_OUTPUT = 1, +}; + +enum aw91xxx_gpio_val { + AW91XXX_GPIO_HIGH = 1, + AW91XXX_GPIO_LOW = 0, +}; + +enum aw91xxx_gpio_output_mode { + AW91XXX_OPEN_DRAIN_OUTPUT = 0, + AW91XXX_PUSH_PULL_OUTPUT = 1, +}; + +struct aw91xxx_singel_gpio { + unsigned int gpio_idx; + enum aw91xxx_gpio_dir gpio_direction; + enum aw91xxx_gpio_val state; + struct aw91xxx *priv; +}; + +struct aw91xxx_gpio { + unsigned int gpio_mask; + unsigned int gpio_num; + enum aw91xxx_gpio_output_mode output_mode; + struct aw91xxx_singel_gpio *single_gpio_data; +}; + +typedef struct { + char name[10]; + int key_code; + int key_val; +} KEY_STATE; + +unsigned int aw91xxx_separate_key_data[AW91XXX_KEY_PORT_MAX] = { +/* 0 1 2 3 */ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 +}; + +struct aw91xxx_key { + unsigned int key_mask; + unsigned int input_port_nums; + unsigned int output_port_nums; + unsigned int input_port_mask; + unsigned int output_port_mask; + unsigned int new_input_state; + unsigned int old_input_state; + unsigned int *new_output_state; + unsigned int *old_output_state; + unsigned int *def_output_state; + bool wake_up_enable; + struct input_dev *input; + + unsigned int debounce_delay; + struct delayed_work int_work; + struct hrtimer key_timer; + struct work_struct key_work; + KEY_STATE *keymap; + int keymap_len; + struct aw91xxx *priv; +}; + +struct aw91xxx { + struct i2c_client *i2c; + struct device *dev; + struct led_classdev cdev; + struct work_struct brightness_work; + struct delayed_work int_work; + + int reset_gpio; + int irq_gpio; + int irq_num; + + unsigned char chipid; + unsigned char vendor_id; + unsigned char blink; + + int imax; + int rise_time; + int on_time; + int fall_time; + int off_time; + + bool led_feature_enable; + bool gpio_feature_enable; + bool matrix_key_enable; + bool single_key_enable; + bool screen_state; + + struct aw91xxx_gpio *gpio_data; + struct aw91xxx_key *key_data; +}; + + +#endif + -- 2.25.1