From patchwork Wed Aug 9 06:39:09 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yuxi Wang X-Patchwork-Id: 712175 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A1717C001B0 for ; Wed, 9 Aug 2023 06:59:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229445AbjHIG7P (ORCPT ); Wed, 9 Aug 2023 02:59:15 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52890 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229814AbjHIG7O (ORCPT ); Wed, 9 Aug 2023 02:59:14 -0400 X-Greylist: delayed 1181 seconds by postgrey-1.37 at lindbergh.monkeyblade.net; Tue, 08 Aug 2023 23:59:12 PDT Received: from mx0a-002bf204.pphosted.com (mx0a-002bf204.pphosted.com [205.220.160.90]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7020A19BC for ; Tue, 8 Aug 2023 23:59:12 -0700 (PDT) Received: from pps.filterd (m0207524.ppops.net [127.0.0.1]) by mx0a-002bf204.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 3795W1PS013090; Tue, 8 Aug 2023 23:39:14 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= monolithicpower.com; h=from:to:cc:subject:date:message-id :content-type:content-transfer-encoding:mime-version; s=pps1; bh=duY11ensR5R2ti2+K0vKljYuCRCuwSTAsaFQTS0WBbM=; b=Mpo+yy1czm73 fqROQykP6UPuo+2QKbbDPtSemSbfjsAAlp+qyKdp+K/xtvx4fzm62Uyuq+Z/tFsw CnluozL9yRRe8UsfCn81uT/ASl4TrwR83Xen9EofrnvJuZ5R8L8lkRYpW3aWyWx5 RArzbcJedch6/BpFKBUxCphJwQ3R2RN7vu1HPy3gdfviSsfJpNCiE/BCXQAdru34 cQkm6pNe8uUM14OtEbf6zv/ALkk/pDgDSAO8LpG/GlaMwgx6/n5fnqQnr92FBUTV Mwu5k6KDBuhDxn0CV708HeSxTJ+4Az/g7pGNUckp1Of/3fPwCwpJlbTOKLX0bjO7 FlCJ6OonDg== Received: from webmail.monolithicpower.com (mps-vpn.monolithicpower.com [12.33.0.20] (may be forged)) by mx0a-002bf204.pphosted.com (PPS) with ESMTPS id 3s9hunssfm-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT); Tue, 08 Aug 2023 23:39:14 -0700 (PDT) Received: from CD-MSH02.monolithicpower.com (10.10.70.19) by mps-mslbn01.monolithicpower.com (10.10.10.199) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.7; Tue, 8 Aug 2023 23:39:12 -0700 Received: from CD-MSH04.monolithicpower.com (10.10.70.213) by CD-MSH02.monolithicpower.com (10.10.70.19) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2375.7; Wed, 9 Aug 2023 14:39:10 +0800 Received: from CD-MSH04.monolithicpower.com ([fe80::fd9c:129e:f4d1:923f]) by CD-MSH04.monolithicpower.com ([fe80::fd9c:129e:f4d1:923f%4]) with mapi id 15.01.2242.004; Wed, 9 Aug 2023 14:39:10 +0800 From: "Yuxi (Yuxi) Wang" To: "pavel@ucw.cz" , "lee@kernel.org" CC: "linux-kernel@vger.kernel.org" , "linux-leds@vger.kernel.org" , "Yuxi (Yuxi) Wang" , "Leal (Long) Li" , "wyx137120466@gmail.com" Subject: [PATCH 1/2] leds: Add driver for mp3326 Thread-Topic: [PATCH 1/2] leds: Add driver for mp3326 Thread-Index: AdnKiRscmSK32XKzR9ChxNBk+6Goqg== Date: Wed, 9 Aug 2023 06:39:09 +0000 Message-ID: <97598d1812a7430d8f11682038ecf6dd@monolithicpower.com> Accept-Language: en-US Content-Language: zh-CN X-MS-Has-Attach: X-MS-TNEF-Correlator: x-originating-ip: [10.10.84.38] MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: fVIdsG5aZnms8I8mvvVRe8qerhZqy6rJ X-Proofpoint-GUID: fVIdsG5aZnms8I8mvvVRe8qerhZqy6rJ X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.957,Hydra:6.0.591,FMLib:17.11.176.26 definitions=2023-08-09_04,2023-08-08_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 spamscore=0 clxscore=1011 mlxscore=0 lowpriorityscore=0 malwarescore=0 mlxlogscore=999 phishscore=0 adultscore=0 impostorscore=0 priorityscore=1501 bulkscore=0 suspectscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2306200000 definitions=main-2308090058 Precedence: bulk List-ID: X-Mailing-List: linux-leds@vger.kernel.org This patch adds a led driver for the mp3326 which is from Monolithic Power Systems, Inc. Signed-off-by: Yuxi Wang --- drivers/leds/Kconfig | 7 + drivers/leds/Makefile | 1 + drivers/leds/leds-mp3326.c | 836 +++++++++++++++++++++++++++++++++++++ 3 files changed, 844 insertions(+) create mode 100644 drivers/leds/leds-mp3326.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 499d0f215a8b..5369506be7b2 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -244,6 +244,13 @@ config LEDS_MT6323 This option enables support for on-chip LED drivers found on Mediatek MT6323 PMIC. +config LEDS_MP3326 + tristate "LED Support for Monolithic power system MP3326" + depends on LEDS_CLASS + help + This option enables support for on-chip LED drivers found on + Monolithic power system MP3326. + config LEDS_S3C24XX tristate "LED Support for Samsung S3C24XX GPIO LEDs" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 4fd2f92cd198..4859959c5c9a 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o +obj-$(CONFIG_LEDS_MP3326) += leds-mp3326.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o diff --git a/drivers/leds/leds-mp3326.c b/drivers/leds/leds-mp3326.c new file mode 100644 index 000000000000..b54e167ea7cf --- /dev/null +++ b/drivers/leds/leds-mp3326.c @@ -0,0 +1,836 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * MP3326 Led driver + * + * Copyright 2023 Monolithic Power Systems, Inc + * + * Author: Yuxi Wang + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*reg list*/ +#define MP3326_PWM_DIM_FREQUENCY_CONFIG 0x00 +#define MP3326_PWM_CTRL 0x01 +#define MP3326_PWM_DIM_FREQUENCY_CONFIG 0x00 +#define MP3326_PWM_CTRL_CHANNEL_9_16 0x04 +#define MP3326_PWM_CTRL_CHANNEL_1_8 0x05 +#define MP3326_PWM_OPEN_FAULT_CHANNEL_9_16 0x06 +#define MP3326_PWM_OPEN_FAULT_CHANNEL_1_8 0x07 +#define MP3326_PWM_SHORT_FAULT_CHANNEL_9_16 0x08 +#define MP3326_PWM_SHORT_FAULT_CHANNEL_1_8 0x09 +#define MP3326_PWM_CURRENT_SET_CHANNEL1 0x0A +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL1 0x0B +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL1 0x0C +#define MP3326_PWM_CURRENT_SET_CHANNEL2 0x0D +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL2 0x0E +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL2 0x0F +#define MP3326_PWM_CURRENT_SET_CHANNEL3 0x10 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL3 0x11 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL3 0x12 +#define MP3326_PWM_CURRENT_SET_CHANNEL4 0x13 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL4 0x14 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL4 0x15 +#define MP3326_PWM_CURRENT_SET_CHANNEL5 0x16 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL5 0x17 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL5 0x18 +#define MP3326_PWM_CURRENT_SET_CHANNEL6 0x19 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL6 0x1A +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL6 0x1B +#define MP3326_PWM_CURRENT_SET_CHANNEL7 0x1C +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL7 0x1D +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL7 0x1E +#define MP3326_PWM_CURRENT_SET_CHANNEL8 0x1F +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL8 0x20 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL8 0x21 +#define MP3326_PWM_CURRENT_SET_CHANNEL9 0x22 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL9 0x23 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL9 0x24 +#define MP3326_PWM_CURRENT_SET_CHANNEL10 0x25 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL10 0x26 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL10 0x27 +#define MP3326_PWM_CURRENT_SET_CHANNEL11 0x28 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL11 0x29 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL11 0x2A +#define MP3326_PWM_CURRENT_SET_CHANNEL12 0x2B +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL12 0x2C +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL12 0x2D +#define MP3326_PWM_CURRENT_SET_CHANNEL13 0x2E +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL13 0x2F +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL13 0x30 +#define MP3326_PWM_CURRENT_SET_CHANNEL14 0x31 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL14 0x32 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL14 0x33 +#define MP3326_PWM_CURRENT_SET_CHANNEL15 0x34 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL15 0x35 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL15 0x36 +#define MP3326_PWM_CURRENT_SET_CHANNEL16 0x37 +#define MP3326_PWM_DUTY_LSB_SET_CHANNEL16 0x38 +#define MP3326_PWM_DUTY_MSB_SET_CHANNEL16 0x39 + +#define MP3326_REG_FIELD(ref) { \ + .regmap_fields = ref##_reg_fields,\ +} + +enum MP3326_Channel { + Channel1, + Channel2, + Channel3, + Channel4, + Channel5, + Channel6, + Channel7, + Channel8, + Channel9, + Channel10, + Channel11, + Channel12, + Channel13, + Channel14, + Channel15, + Channel16, + Max_Channel, +}; + +enum RGB_CTRL { +ENABLE, +BRIGHTNESS, +COLOR_H4, +COLOR_L8, +OPEN_FAULT, +SHORT_FAULT, +Max_CTRL, +}; + +struct MP3326_reg_field { + struct reg_field *regmap_fields; +}; + +static struct reg_field channel1_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 0, 0), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL1, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL1, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL1, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 0, 0), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 0, 0), +}; + +static struct reg_field channel2_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 1, 1), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL2, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL2, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL2, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 1, 1), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 1, 1), +}; + +static struct reg_field channel3_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 2, 2), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL3, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL3, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL3, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 2, 2), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 2, 2), +}; + +static struct reg_field channel4_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 3, 3), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL4, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL4, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL4, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 3, 3), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 3, 3), +}; + +static struct reg_field channel5_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 4, 4), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL5, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL5, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL5, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 4, 4), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 4, 4), +}; + +static struct reg_field channel6_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 5, 5), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL6, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL6, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL6, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 5, 5), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 5, 5), +}; + +static struct reg_field channel7_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 6, 6), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL7, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL7, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL7, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 6, 6), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 6, 6), +}; + +static struct reg_field channel8_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_1_8, 7, 7), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL8, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL8, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL8, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_1_8, 7, 7), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_1_8, 7, 7), +}; + +static struct reg_field channel9_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 0, 0), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL9, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL9, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL9, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 0, 0), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 0, 0), +}; + +static struct reg_field channel10_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 1, 1), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL10, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL10, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL10, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 1, 1), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 1, 1), +}; + +static struct reg_field channel11_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 2, 2), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL11, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL11, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL11, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 2, 2), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 2, 2), +}; + +static struct reg_field channel12_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 3, 3), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL12, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL12, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL12, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 3, 3), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 3, 3), +}; + +static struct reg_field channel13_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 4, 4), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL13, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL13, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL13, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 4, 4), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 4, 4), +}; + +static struct reg_field channel14_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 5, 5), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL14, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL14, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL14, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 5, 5), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 5, 5), +}; + +static struct reg_field channel15_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 6, 6), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL15, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL15, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL15, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 6, 6), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 6, 6), +}; + +static struct reg_field channel16_reg_fields[] = { + [ENABLE] = REG_FIELD(MP3326_PWM_CTRL_CHANNEL_9_16, 7, 7), + [BRIGHTNESS] = REG_FIELD(MP3326_PWM_CURRENT_SET_CHANNEL16, 0, 5), + [COLOR_H4] = REG_FIELD(MP3326_PWM_DUTY_LSB_SET_CHANNEL16, 0, 3), + [COLOR_L8] = REG_FIELD(MP3326_PWM_DUTY_MSB_SET_CHANNEL16, 0, 7), + [OPEN_FAULT] = REG_FIELD(MP3326_PWM_OPEN_FAULT_CHANNEL_9_16, 7, 7), + [SHORT_FAULT] = REG_FIELD(MP3326_PWM_SHORT_FAULT_CHANNEL_9_16, 7, 7), +}; + +static const struct MP3326_reg_field MP3266_reg_fields[] = { + [Channel1] = MP3326_REG_FIELD(channel1), + [Channel2] = MP3326_REG_FIELD(channel2), + [Channel3] = MP3326_REG_FIELD(channel3), + [Channel4] = MP3326_REG_FIELD(channel4), + [Channel5] = MP3326_REG_FIELD(channel5), + [Channel6] = MP3326_REG_FIELD(channel6), + [Channel7] = MP3326_REG_FIELD(channel7), + [Channel8] = MP3326_REG_FIELD(channel8), + [Channel9] = MP3326_REG_FIELD(channel9), + [Channel10] = MP3326_REG_FIELD(channel10), + [Channel11] = MP3326_REG_FIELD(channel11), + [Channel12] = MP3326_REG_FIELD(channel12), + [Channel13] = MP3326_REG_FIELD(channel13), + [Channel14] = MP3326_REG_FIELD(channel14), + [Channel15] = MP3326_REG_FIELD(channel15), + [Channel16] = MP3326_REG_FIELD(channel16), +}; + +struct RGB { +struct led_classdev cdev; +struct MP3326_Led *chip; +int red; +int green; +int blue; +int AnalogDim; +int PWMDim; +bool IsAlive; +}; + +static const struct regmap_config MP3326_regmap_config = { +.reg_bits = 8, +.val_bits = 8, +}; + +struct ChannelField { + struct regmap_field *regmap_fields[Max_CTRL]; +}; + +struct MP3326_Led { +struct i2c_client *client; +struct regmap *regmap; +struct ChannelField *channel_fields[Max_Channel]; +struct mutex mutex; +u32 num_rgbs; +struct RGB rgbs[]; +}; + +/* + * Set the brightness. + */ +static int MP3326_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct RGB *led = container_of(led_cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + int ret; + + if (value > 63) + led->AnalogDim = 63; + else + led->AnalogDim = value; + + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + return 0; +} + +/* + * Show the current pwm dim value which is decimal. + */ +static ssize_t pwm_Dim_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + unsigned int val; + int rval = 0, gval = 0, bval = 0; + + regmap_field_read(chip->channel_fields[led->red]->regmap_fields[COLOR_L8], &val); + rval |= val << 4; + regmap_field_read(chip->channel_fields[led->red]->regmap_fields[COLOR_H4], &val); + rval |= val; + val = 0; + regmap_field_read(chip->channel_fields[led->green]->regmap_fields[COLOR_L8], &val); + gval |= val << 4; + regmap_field_read(chip->channel_fields[led->green]->regmap_fields[COLOR_H4], &val); + gval |= val; + val = 0; + regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[COLOR_L8], &val); + bval |= val << 4; + regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[COLOR_H4], &val); + bval |= val; + + rval = rval * 255 / 4095 + ((rval * 255) % 4095 > 2047 ? 1 : 0); + gval = gval * 255 / 4095 + ((gval * 255) % 4095 > 2047 ? 1 : 0); + bval = bval * 255 / 4095 + ((bval * 255) % 4095 > 2047 ? 1 : 0); + + + return sysfs_emit(buf, "%d %d %d\n", rval, gval, bval); +} + +/* + * PWM dimming, here are two input ways. + * The first: Input '200 200 200' which sequence is red green blue and decimal. + * The Second: Input 'H 0f 0f 0f' which sequence is red green blue and Hex. + */ +static ssize_t pwm_Dim_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + ssize_t ret; + int r_val, g_val, b_val; + + ret = sscanf(buf, "%d %d %d", &r_val, &g_val, &b_val); + if (ret != 3) { + ret = sscanf(buf, "H %x %x %x", &r_val, &g_val, &b_val); + if (ret != 3) + return ret; + } + r_val = r_val * 4095 / 255 + ((r_val * 4095) % 255 > 127 ? 1 : 0); + g_val = g_val * 4095 / 255 + ((g_val * 4095) % 255 > 127 ? 1 : 0); + b_val = b_val * 4095 / 255 + ((b_val * 4095) % 255 > 127 ? 1 : 0); + + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[COLOR_H4], r_val & 0x0f); + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[COLOR_L8], r_val >> 4); + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[COLOR_H4], g_val & 0x0f); + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[COLOR_L8], g_val >> 4); + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[COLOR_H4], b_val & 0x0f); + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[COLOR_L8], b_val >> 4); + + if (ret) + return -EINVAL; + return count; +} + +/* + *Show an integer which indicates current brightness. + */ +static ssize_t analog_Dim_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + unsigned int val; + + regmap_field_read(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], &val); + + return sysfs_emit(buf, "%u\n", val); +} +/* + * Input an integer value which changes the brightness. + * The integer is range from 0 to 63 + */ +static ssize_t analog_Dim_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + ssize_t ret; + long long_val; + + ret = kstrtol(buf, 10, &long_val); + if (long_val < 0) + return -EINVAL; + if (long_val > cdev->max_brightness) { + led->AnalogDim = cdev->max_brightness; + cdev->brightness = cdev->max_brightness; + } else { + led->AnalogDim = long_val; + cdev->brightness = long_val; + } + + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + return count; +} + +/* + * Show a string which indicates enable all channels of rgb. + * True indicates all channels of rgb enable. + * False indicates all channels of rgb disable. + */ +static ssize_t rgb_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + unsigned int rval = 0, val; + + regmap_field_read(chip->channel_fields[led->red]->regmap_fields[ENABLE], &val); + rval |= val; + regmap_field_read(chip->channel_fields[led->green]->regmap_fields[ENABLE], &val); + rval |= val; + regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[ENABLE], &val); + rval |= val; + regmap_read(chip->regmap, 0x05, &val); + if (rval) + return sysfs_emit(buf, "%s\n", "True"); + + return sysfs_emit(buf, "%s\n", "False"); +} + +/* + * Input 1 or 0 to control the channels of rgb enable/disable. + * If input 1, the channels of rgb is enable + * If input 0, the channels of rgb is disable + */ +static ssize_t rgb_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + ssize_t ret; + long long_val; + + ret = kstrtol(buf, 10, &long_val); + if (long_val) { + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[BRIGHTNESS], led->AnalogDim); + if (ret) + return -EINVAL; + + + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[ENABLE], long_val); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[ENABLE], long_val); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[ENABLE], long_val); + if (ret) + return -EINVAL; + + led->IsAlive = true; + } else { + ret = regmap_field_write(chip->channel_fields[led->red]->regmap_fields[ENABLE], long_val); + if (ret) + return -EINVAL; + + + ret = regmap_field_write(chip->channel_fields[led->green]->regmap_fields[ENABLE], long_val); + if (ret) + return -EINVAL; + + ret = regmap_field_write(chip->channel_fields[led->blue]->regmap_fields[ENABLE], long_val); + if (ret) + return -EINVAL; + + led->IsAlive = false; + } + + return count; +} + +/* + * Show a string which indicates short fault of rgb. + * True indicates the rgb has a short fault. + * False indicates the rgb is normal. + */ +static ssize_t short_fault_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + unsigned int rval = 0, val; + + regmap_field_read(chip->channel_fields[led->red]->regmap_fields[SHORT_FAULT], &val); + rval |= val; + regmap_field_read(chip->channel_fields[led->green]->regmap_fields[SHORT_FAULT], &val); + rval |= val; + regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[SHORT_FAULT], &val); + rval |= val; + if (rval) + return sysfs_emit(buf, "%s\n", "True"); + + return sysfs_emit(buf, "%s\n", "False"); +} + +/* + * Show a string which indicates open load fault of rgb. + * True indicates the rgb has an open load fault. + * False indicates the rgb is normal. + */ +static ssize_t open_fault_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct RGB *led = container_of(cdev, struct RGB, cdev); + struct MP3326_Led *chip = led->chip; + unsigned int rval = 0, val; + + regmap_field_read(chip->channel_fields[led->red]->regmap_fields[OPEN_FAULT], &val); + rval |= val; + regmap_field_read(chip->channel_fields[led->green]->regmap_fields[OPEN_FAULT], &val); + rval |= val; + regmap_field_read(chip->channel_fields[led->blue]->regmap_fields[OPEN_FAULT], &val); + rval |= val; + + if (rval) + return sysfs_emit(buf, "%s\n", "True"); + + return sysfs_emit(buf, "%s\n", "False"); +} + + +static DEVICE_ATTR_RW(rgb_enable); +static DEVICE_ATTR_RW(pwm_Dim); +static DEVICE_ATTR_RW(analog_Dim); +static DEVICE_ATTR_RO(open_fault); +static DEVICE_ATTR_RO(short_fault); +static struct attribute *MP3266_led_sysfs_attrs[] = { + &dev_attr_rgb_enable.attr, + &dev_attr_pwm_Dim.attr, + &dev_attr_analog_Dim.attr, + &dev_attr_open_fault.attr, + &dev_attr_short_fault.attr, + NULL, +}; +ATTRIBUTE_GROUPS(MP3266_led_sysfs); + +static int MP3266_probe_fw(struct device *dev, struct MP3326_Led *chip) +{ + struct fwnode_handle *child; + int ret; + int i; + + device_for_each_child_node(dev, child) { + struct RGB *rgb; + struct led_init_data init_data = {}; + u32 source, Red = 0, Green, Blue; + + ret = fwnode_property_read_u32(child, "rgb_r", &Red); + if (ret) { + dev_err(dev, "Missing rgb_r property\n"); + chip->num_rgbs--; + continue; + } + + if (Red <= 0 || Red > Max_Channel) { + dev_err(dev, "LED reg %u out of range (max %u)(min 1)\n", + Red, Max_Channel); + chip->num_rgbs--; + continue; + } + + ret = fwnode_property_read_u32(child, "rgb_g", &Green); + + if (ret) { + dev_err(dev, "Missing rgb_g property\n"); + chip->num_rgbs--; + continue; + } + + if (Green <= 0 || Green > Max_Channel) { + dev_err(dev, "LED reg %u out of range (max %u)(min 1)\n", + Green, Max_Channel); + chip->num_rgbs--; + continue; + } + + ret = fwnode_property_read_u32(child, "rgb_b", &Blue); + + if (ret) { + dev_err(dev, "Missing rgb_b property\n"); + chip->num_rgbs--; + continue; + } + + if (Blue <= 0 || Blue > Max_Channel) { + dev_err(dev, "LED reg %u out of range (max %u)(min 1)\n", + Blue, Max_Channel); + chip->num_rgbs--; + continue; + } + + ret = fwnode_property_read_u32(child, "brightness", &source); + + if (ret) { + dev_err(dev, "Missing brightness property\n"); + chip->num_rgbs--; + continue; + } + + if (source < 0) { + dev_err(dev, "Brightness: %u must be greater than 0\n", + source); + chip->num_rgbs--; + continue; + } else if (source > 63) { + source = 63; + } + rgb = &chip->rgbs[i]; + rgb->chip = chip; + rgb->red = Red-1; + rgb->green = Green-1; + rgb->blue = Blue-1; + rgb->AnalogDim = source; + + rgb->cdev.groups = MP3266_led_sysfs_groups; + rgb->cdev.brightness_set_blocking = MP3326_led_set; + rgb->cdev.brightness = source; + rgb->cdev.max_brightness = 63; + init_data.fwnode = child; + + ret = devm_led_classdev_register_ext(dev, &rgb->cdev, + &init_data); + if (ret) { + fwnode_handle_put(child); + break; + } + i++; + ret = regmap_field_write(chip->channel_fields[rgb->red]->regmap_fields[BRIGHTNESS], source); + ret = regmap_field_write(chip->channel_fields[rgb->green]->regmap_fields[BRIGHTNESS], source); + ret = regmap_field_write(chip->channel_fields[rgb->blue]->regmap_fields[BRIGHTNESS], source); + } + if (!chip->num_rgbs) + return -EINVAL; + return 0; +} + +static int MP3326_leds_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct MP3326_Led *chip; + struct ChannelField *channel; + struct device_node *np = client->dev.of_node; + int ret, count, source; + int i, j; + + count = device_get_child_node_count(&client->dev); + if (!count || count > 5) { + return dev_err_probe(&client->dev, -EINVAL, + "Incorrect number of rgbs (%d)", count); + } + + chip = devm_kzalloc(&client->dev, struct_size(chip, rgbs, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + channel = devm_kzalloc(&client->dev, sizeof(struct ChannelField) * Max_Channel, GFP_KERNEL); + if (!channel) + return -ENOMEM; + + chip->client = client; + chip->num_rgbs = count; + + i2c_set_clientdata(client, chip); + chip->regmap = devm_regmap_init_i2c(client, &MP3326_regmap_config); + if (IS_ERR(chip->regmap)) + return PTR_ERR(chip->regmap); + + + for (i = 0; i < Max_Channel; i++) { + for (j = 0; j < Max_CTRL; j++) { + channel[i].regmap_fields[j] = devm_regmap_field_alloc(&client->dev, + chip->regmap, MP3266_reg_fields[i].regmap_fields[j]); + + if (IS_ERR(channel[i].regmap_fields[j])) { + dev_err(&client->dev, "regmap field alloc fail %d\n", i); + return PTR_ERR(channel[i].regmap_fields[j]); + } + } + chip->channel_fields[i] = &channel[i]; + channel++; + } + + if (IS_ERR(chip->channel_fields)) + return PTR_ERR(chip->channel_fields); + + ret = of_property_read_u32(np, "led-protect", &source); + if (!ret) + ret = regmap_update_bits(chip->regmap, 0x01, BIT(4) | BIT(5), source<<4); + + ret = of_property_read_u32(np, "switch_status", &source); + if (ret) + regmap_update_bits(chip->regmap, 0x01, BIT(0), 0); + else + regmap_update_bits(chip->regmap, 0x01, BIT(0), source); + + /*close all channel*/ + regmap_write(chip->regmap, MP3326_PWM_CTRL_CHANNEL_9_16, 0); + regmap_write(chip->regmap, MP3326_PWM_CTRL_CHANNEL_1_8, 0); + mutex_init(&chip->mutex); + mutex_lock(&chip->mutex); + + ret = MP3266_probe_fw(&client->dev, chip); + if (ret) + goto out_unlock; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int MP3326_leds_remove(struct i2c_client *client) +{ + return 0; +} + +static const struct i2c_device_id MP3326_led_id[] = { + {"MP3326", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, MP3326_led_id); + +static const struct of_device_id MP3326_led_of_match[] = { + { .compatible = "mps,MP3326" }, + {} +}; +MODULE_DEVICE_TABLE(of, MP3326_led_of_match); + +static struct i2c_driver MP3326_leds_driver = { + .probe = MP3326_leds_probe, + .remove = MP3326_leds_remove, + .driver = { + .owner = THIS_MODULE, + .name = "MP3326_led", + .of_match_table = MP3326_led_of_match, + }, + + .id_table = MP3326_led_id, +}; + +module_i2c_driver(MP3326_leds_driver); + +MODULE_AUTHOR("Yuxi Wang "); +MODULE_DESCRIPTION("MP3326 Led driver"); +MODULE_LICENSE("GPL");