From nobody Fri Dec 19 15:50:24 2025 Received: from mail-pf1-f174.google.com (mail-pf1-f174.google.com [209.85.210.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9DB0324633C; Sun, 27 Apr 2025 08:25:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742355; cv=none; b=BCIQv3/lm/F6nPDFgk9sVwPzfkBzwfZdzJaRYOpFTeeXehZE55nNUWikqsa1vWwuWAw85iycanY2yQ4/WDOpaqAIDsxzltJa1cB2nvLqUkshf6znuCRR4cm1pyX/fe4TGGjxu4kb8uiPGZJrfV6/L+hpg+hK/CT6XX3SIznVx+8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742355; c=relaxed/simple; bh=eVqAbRf2LU81j02LwChbq9MHSwHQPr/AP8WU7YYB9Ss=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=Zi48waVuKe1Bbz+1vl05vkgAyNrNG6qGCLwjf6oLtCbtXe7ZCRyq+PUfSulfNv3AjW8knkcbjLpRTEb417Qc1DlsmW5nrwd0etVFLxA7di0d5ktrDl8nA/iees78nyKXhOhpZ8ICeuQwF+m0EV4n/GIZuwDwI4cmeDdvIj55T58= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ePAq7bbn; arc=none smtp.client-ip=209.85.210.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ePAq7bbn" Received: by mail-pf1-f174.google.com with SMTP id d2e1a72fcca58-736c277331eso4550334b3a.1; Sun, 27 Apr 2025 01:25:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745742352; x=1746347152; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=pFIDnjjBixCsqIHSTkUHSF/akN3O0qOzMZ/jw6UNaRw=; b=ePAq7bbnt7xFx3ZY94sqoqTkvAXU80W5oAcvjLfBfxEV4x+m+iGkejjr7dbDcoRptQ dICBAG2MEbGlYYrEejE4CmwUOosa+BdGehFjoY2cygbDYLxmWTAgbvSuQFBcIv0EQc8U 7SlmjnN7gnTSH/9HPPGYl0WbXxK4EhW+39IDo9fLGhndstBB0thnx9kcmAU3fw/03A/s hRCOnsyphLfga6xWJVYPlVW/b6NHAIzzfsywSHnmmJo/gb/pv5AK1T6aMR3wYdE9l6gk IH/zABDvAvHGFsN3+v9UjWoS5DCXw1R3e2IZbS+cDV+XOdkCnkME9SjDbJd0WDggsSse tLog== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745742352; x=1746347152; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=pFIDnjjBixCsqIHSTkUHSF/akN3O0qOzMZ/jw6UNaRw=; b=dsmHV5SgzHl0mWcdg7LF7wiYsXQxQQMzOr+nNTzBI4fUvQXuGNqhyWTp1Q14F/pLhy NIfqzzUpfasmu9n1WILRA1hGHXoH8DJ3mcNKzaTaVIjOMlDLrBH8feBI194N+/eUxpdp yhJ7FjNme1wFwB1l9SXfhvobahAIMIppd/Ft0c5dx2xRRp88qnJc9nKk/elqmFCwRiKC KBDt9wjL40qgHvBe+vB6iNf0byq4q2Iim4wy+S9QRvZYdzG9d6FIrYVSlLDSESxEVy2S DtrW5pG7MmFwHgdA3AA+07tqvK5Teqj9cHWYVFNQABPV8O4aRJd5p9leHz0wX137V9A4 gDuw== X-Forwarded-Encrypted: i=1; AJvYcCVtaPErhGELnWeK7VFNSgBvj3HZLX0GRdFpT0JSiIdBqQe6A4bRezx++7a1hBL4BSZZlp4GnaTt9dzoAj+O@vger.kernel.org, AJvYcCXDfsinQH8cRWXnHN2qo9JkhWIYmQKewOlpDSKXxaAAPzQRnfJ+/grJQRw0JGkpMva1KH9ZCnWhUtar@vger.kernel.org, AJvYcCXNbmnINrK+OFPhTjTz4arvrfq8hpI3X9M99PdKCU3oWIyk0N9GkLqY1A38FAtVwZjmtjrZntWTcf2V@vger.kernel.org X-Gm-Message-State: AOJu0YzK2NTlGmMvcwRAUa4Y+yEnta7d/3pnB6U1Sv4s6Tm9we5lYx71 /eHZF4xkoq7e77zNRZurryXy4e/cMeJCmZcrsUExtEtex4a7xhdU X-Gm-Gg: ASbGncuuUZQVARtt9KmRORJPN3Wm4loLJMsyMs38kR4DiTKMla32Y4I+gOXa663h9Ti bJVcdDV2Fop/GSfTa1dob5027aDA5M25BnwuMKD8Ef2g+4jMDpFeyLaZ8REH1/z43iS9Cgem/k9 QcDN0mnst5Zdvs8R/RB0VlLkQFNHr1AT+oEZwOtMMSEtAZMF1dTUE+QtKX0aR2qIs4KUAjeKGr1 DXDVTiPtNm1Kc1tKlKLDXKm6eVcmhoHIgs+Hrx4qfD4cCPY+/qPv+qbjB8zY/0o1c/sCf2SFZJA 841V+tInpuKDMB5SmCls0VXNyTNdGaTpab2MSJo2Ji5AyxNfgVkFiA== X-Google-Smtp-Source: AGHT+IEE9tESNitS340243NR75TTRy3mJOc1Opl+pT6NC9QcFHEBSD+U1fuYWl2jRI5ij6l5EOhyeA== X-Received: by 2002:a05:6a20:3d24:b0:1f3:26ae:7792 with SMTP id adf61e73a8af0-2045b5f6042mr9104900637.18.1745742351816; Sun, 27 Apr 2025 01:25:51 -0700 (PDT) Received: from localhost.localdomain ([14.171.43.210]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-73e2549f570sm5900852b3a.0.2025.04.27.01.25.47 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Apr 2025 01:25:51 -0700 (PDT) From: Nam Tran To: andy@kernel.org Cc: geert@linux-m68k.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, christophe.jaillet@wanadoo.fr, corbet@lwn.net, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, florian.fainelli@broadcom.com, bcm-kernel-feedback-list@broadcom.com, linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org, Nam Tran , Krzysztof Kozlowski Subject: [PATCH v8 1/5] =?UTF-8?q?dt-bindings:=20auxdisplay:=20add=20TI=20?= =?UTF-8?q?LP5812=204=C3=973=20Matrix=20RGB=20LED=20Driver?= Date: Sun, 27 Apr 2025 15:24:43 +0700 Message-Id: <20250427082447.138359-2-trannamatk@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20250427082447.138359-1-trannamatk@gmail.com> References: <20250427082447.138359-1-trannamatk@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The LP5812 is a 4=C3=973 RGB LED driver with an autonomous animation engine and time-cross-multiplexing (TCM) support for up to 12 LEDs. It supports both analog (256 levels) and PWM (8-bit) dimming, including exponential PWM for smooth brightness control. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Nam Tran --- .../bindings/auxdisplay/ti,lp5812.yaml | 46 +++++++++++++++++++ MAINTAINERS | 6 +++ 2 files changed, 52 insertions(+) create mode 100644 Documentation/devicetree/bindings/auxdisplay/ti,lp5812.= yaml diff --git a/Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml b/= Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml new file mode 100644 index 000000000000..fb94a6336e16 --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/auxdisplay/ti,lp5812.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI LP5812 4=C3=973 Matrix RGB LED Driver with Autonomous Control + +maintainers: + - Nam Tran + +description: | + The LP5812 is an I2C LED Driver that can support LED matrix 4x3. + For more product information please see the link below: + https://www.ti.com/product/LP5812#tech-docs + +properties: + compatible: + const: ti,lp5812 + + reg: + maxItems: 1 + + vcc-supply: + description: Regulator providing power to the 'VCC' pin. + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + led-controller@1b { + compatible =3D "ti,lp5812"; + reg =3D <0x1b>; + vcc-supply =3D <&vdd_3v3_reg>; + }; + }; + +... diff --git a/MAINTAINERS b/MAINTAINERS index 7940ddd91196..241c5441e239 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24016,6 +24016,12 @@ S: Supported F: Documentation/devicetree/bindings/iio/dac/ti,dac7612.yaml F: drivers/iio/dac/ti-dac7612.c =20 +TEXAS INSTRUMENTS' LP5812 4X3 MATRIX LED DRIVER +M: Nam Tran +L: linux-kernel@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml + TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER M: Alexander Sverdlin L: linux-leds@vger.kernel.org --=20 2.25.1 From nobody Fri Dec 19 15:50:24 2025 Received: from mail-pf1-f174.google.com (mail-pf1-f174.google.com [209.85.210.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 836092475C7; Sun, 27 Apr 2025 08:26:08 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742372; cv=none; b=Pg/0w/7oTjJ9AOFtkOyYsx/RricL9KJekSbNYCcX3YoRrvMresvFObu1WzxKu9djN7MK47H5+k89qHRXyKTc5kiPejpWU4VyorT7HliH2V2LfHP6BwobmA5NOmH1cbMLKVFYgfUxSY1Km2OYkHm+vWB9jk+2MwYnqVbE24AXIGY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742372; c=relaxed/simple; bh=t6arqzdUaOtv9pPyIdodK1EqtG+GCOCm+GTb9Z+ftz8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=fKa3MO7rVhCSY6+vkyqQB0oflBiGREgbYpIZu4Oy6H8js0t1xrETzqhYf7sQS2BZjYTIG2HWaIciyCkFnQuvKOgHiKuqx51eNQvDJbzac31tECuHewJ2PSu3Om33kjfAsQTUTnIrko5JHOOW5+B0fbx41vZVaBbo5IQ25C/dnyc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=RxfP5299; arc=none smtp.client-ip=209.85.210.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="RxfP5299" Received: by mail-pf1-f174.google.com with SMTP id d2e1a72fcca58-736c277331eso4550453b3a.1; Sun, 27 Apr 2025 01:26:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745742368; x=1746347168; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=oGiSzGF95kNB3xXzCl0tOsR7A7/3r/2o7cJ0bGtCucc=; b=RxfP5299DbYtazLIbR8esDhIc9SQbjKMhJXK/96dSAqmq64kpwTq5EvqP8ozbTXeCZ JWW1+O0eYYh/u2ig/6e0bqFsdwVw6rpevUlPEb3Sgb0RATRsJlL8vdgSc9Z0yw9cKxmB zX8PUIZ2STBceHJ+0W3DRi0DL/LYNQLp+jL1leQ1IqT0RuPWr4xpfsEkpSLLSM5BDhDb iF8iAyyttdxZyksuATLdItywJl8vv5xKN7IE0/lcT+HEImeKdRENIeORitYdoLGI9dLT 5VHCBdqOh979bL6O+pQ4ybVJGnJ2QQq9Lu4tSDuM1o8JCzoJTZ6qo77EG43MyWSQUG5a 8Qsg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745742368; x=1746347168; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=oGiSzGF95kNB3xXzCl0tOsR7A7/3r/2o7cJ0bGtCucc=; b=XpzkLHJDu5xcoaNYmoZgH5aeMh+oHyDSKUcvgjph0BiwZ/Tpin++0o2LHBFWChY9Q+ WZ6kabS4HnJaLitxdo7zUfAOGj7+40CUMjHsByGA/UMtDBnYIi4bUfcqMrSvJuI/DLGy KKeC+BF9dKAGnk5W9mjeDLaEGIEuIkH/yL+obxKq8kA6gROb8S9PRUQcf12VbjOP7uRi Y06hFpjgE0zmWb1PoRJ+dNcRu1L8sBO734BzjZkXv3X5lbznD/5UpDu/xLgoDAd7nzKs 0LwJps6C6DCDEnsoo1+1/tuSPffTdVk27NCCRBH+X+vAZ2mU3TIkXv+jp1pec9R8R6wH OpvA== X-Forwarded-Encrypted: i=1; AJvYcCWGohpKj5a9vKDh0LtXQRtAmMqj50zURnlcrENP6FyvOq6eM3SE4gFUSG5kdWpoNVy0mjxfx6X8SHGW@vger.kernel.org, AJvYcCWvGAQJ60b3DBJ0Syf2fO5jzoRbe2uhGn1Aqv2AQMo8Bsm4JU38nYnB7qxXzE0PykIhqlUkFCxqSreg@vger.kernel.org, AJvYcCXV1WtmgQ8yjZt33ZSsMRN/QuvMTUNU1UFWrfN5ijYHwzhIUEZ9HO4KTpJeL9Ep654hYQv2AYJ0WyJP/IBJ@vger.kernel.org X-Gm-Message-State: AOJu0YxyZ1RLDN15/Ll16Atr9VsT2zyOZRyqmg1qndaeAP3aYN+QjOzc hSYjidJDgr06/HjG1NNsm4PUoB54yeneUeg5qLypVYfDFv8BPXdo X-Gm-Gg: ASbGncsZ0YRULvqNGx31fUgHBsBu9tYN4yw3kkiVyqYmQys9I5hqEBbJIx3w50gYdZB 0JYTcqpWnX8vmYtMJ3WKL2hNgjQJPZqFth36T020hfedEx0SyoAfCdMdOKGHm80jDGHqJEC4AAD DntJfn3oOSEJRuxvQHEVa3Dn43CgXtF/7kFNG/JVmVYpHvNvErlifBH7Vs9/i/AYenDfOt80M62 tozp55PId/PjYbSoyHfQuQak78bABr0+oEldZHWXs++Ji/dzAtBYS7FSD8NCzACe0rnHsBsq1oy 6a+5tzIiZBcjerdmJitU1uj4JEqIIfLstjc1f8yXHVVThwIIeKAkfg== X-Google-Smtp-Source: AGHT+IFer68z9u9TDBcXaYdC7tnX5CGeAlxUSfX2PdvX4NvkI1InqDtF7JZ1VXaSKkY1fXW/EwBASw== X-Received: by 2002:a05:6a20:ce45:b0:1f5:8cf7:de4b with SMTP id adf61e73a8af0-2045b5adefdmr10584331637.16.1745742367222; Sun, 27 Apr 2025 01:26:07 -0700 (PDT) Received: from localhost.localdomain ([14.171.43.210]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-73e2549f570sm5900852b3a.0.2025.04.27.01.26.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Apr 2025 01:26:06 -0700 (PDT) From: Nam Tran To: andy@kernel.org Cc: geert@linux-m68k.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, christophe.jaillet@wanadoo.fr, corbet@lwn.net, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, florian.fainelli@broadcom.com, bcm-kernel-feedback-list@broadcom.com, linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org, Nam Tran Subject: [PATCH v8 2/5] =?UTF-8?q?auxdisplay:=20add=20TI=20LP5812=204?= =?UTF-8?q?=C3=973=20Matrix=20RGB=20LED=20Driver?= Date: Sun, 27 Apr 2025 15:24:44 +0700 Message-Id: <20250427082447.138359-3-trannamatk@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20250427082447.138359-1-trannamatk@gmail.com> References: <20250427082447.138359-1-trannamatk@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The LP5812 is a 4=C3=973 matrix RGB LED driver with an autonomous animation engine and time-cross-multiplexing (TCM) support for up to 12 LEDs. Each LED can be configured through the related registers to realize vivid and fancy lighting effects. Signed-off-by: Nam Tran --- MAINTAINERS | 4 + drivers/auxdisplay/Kconfig | 17 + drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/lp5812.c | 2736 +++++++++++++++++++++++++++++++++++ drivers/auxdisplay/lp5812.h | 348 +++++ 5 files changed, 3106 insertions(+) create mode 100644 drivers/auxdisplay/lp5812.c create mode 100644 drivers/auxdisplay/lp5812.h diff --git a/MAINTAINERS b/MAINTAINERS index 241c5441e239..091373b01dc2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24021,6 +24021,10 @@ M: Nam Tran L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml +F: drivers/auxdisplay/Kconfig +F: drivers/auxdisplay/Makefile +F: drivers/auxdisplay/lp5812.c +F: drivers/auxdisplay/lp5812.h =20 TEXAS INSTRUMENTS' LB8864 LED BACKLIGHT DRIVER M: Alexander Sverdlin diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index bedc6133f970..896f02b9a06b 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -539,6 +539,23 @@ config ARM_CHARLCD line and the Linux version on the second line, but that's still useful. =20 +# +# TI LP5812 matrix RGB LED driver section +# +config LP5812 +tristate "Enable LP5812 support LED matrix 4x3" +depends on I2C +help + If you say Y here you get support for TI LP5812 LED driver. + + The LP5812 is a 4 =C3=97 3 matrix RGB LED driver with autonomous + animation engine control. + + To compile this driver as a module, choose M here: the + module will be called lp5812. + + If unsure, say N. + endif # AUXDISPLAY =20 # diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index f5c13ed1cd4f..36b9bb5cb1cf 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_IMG_ASCII_LCD) +=3D img-ascii-lcd.o obj-$(CONFIG_KS0108) +=3D ks0108.o obj-$(CONFIG_LCD2S) +=3D lcd2s.o obj-$(CONFIG_LINEDISP) +=3D line-display.o +obj-$(CONFIG_LP5812) +=3D lp5812.o obj-$(CONFIG_MAX6959) +=3D max6959.o obj-$(CONFIG_PARPORT_PANEL) +=3D panel.o obj-$(CONFIG_SEG_LED_GPIO) +=3D seg-led-gpio.o diff --git a/drivers/auxdisplay/lp5812.c b/drivers/auxdisplay/lp5812.c new file mode 100644 index 000000000000..8491939cf395 --- /dev/null +++ b/drivers/auxdisplay/lp5812.c @@ -0,0 +1,2736 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LP5812 LED driver + * + * Copyright (C) 2025 Texas Instruments + * + * Author: Jared Zhou + */ + +#include +#include +#include +#include +#include + +#include "lp5812.h" + +#define to_lp5812_led(x) container_of(x, struct lp5812_led, kobj) +#define to_anim_engine_unit(x) container_of(x, struct anim_engine_unit, ko= bj) + +static int lp5812_init_dev_config(struct lp5812_chip *chip, + const char *drive_mode, int rm_led_sysfs); + +static const struct drive_mode_led_map chip_leds_map[] =3D { + { + "direct_mode", + (const char *[]){LED0, LED1, LED2, LED3, NULL} + }, + { + "tcmscan:1:0", /* tcm 1 scan; scan order 0 out0 */ + (const char *[]){LED_A0, LED_A1, LED_A2, NULL} + }, + { + "tcmscan:1:1", /* tcm 1 scan; scan order 0 out1 */ + (const char *[]){LED_B0, LED_B1, LED_B2, NULL} + }, + { + "tcmscan:1:2", /* tcm 1 scan; scan order 0 out2 */ + (const char *[]){LED_C0, LED_C1, LED_C2, NULL} + }, + { + "tcmscan:1:3", /* tcm 1 scan; scan order 0 out3 */ + (const char *[]){LED_D0, LED_D1, LED_D2, NULL} + }, + { /* tcm 2 scan, scan order 0 out0; scan order 1 out1 */ + "tcmscan:2:0:1", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2, NULL} + }, + { /* tcm 2 scan, scan order 0 out0; scan order 1 out2 */ + "tcmscan:2:0:2", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_C0, LED_C1, LED_C2, NULL} + }, + { /* tcm 2 scan, scan order 0 out0; scan order 1 out3 */ + "tcmscan:2:0:3", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_D0, LED_D1, LED_D2, NULL} + }, + { /* tcm 2 scan, scan order 0 out1; scan order 1 out2 */ + "tcmscan:2:1:2", + (const char *[]){LED_B0, LED_B1, LED_B2, LED_C0, LED_C1, LED_C2, NULL} + }, + { /* tcm 2 scan, scan order 0 out1; scan order 1 out3 */ + "tcmscan:2:1:3", + (const char *[]){LED_B0, LED_B1, LED_B2, LED_D0, LED_D1, LED_D2, NULL} + }, + { /* tcm 2 scan, scan order 0 out2; scan order 1 out3 */ + "tcmscan:2:2:3", + (const char *[]){LED_C0, LED_C1, LED_C2, LED_D0, LED_D1, LED_D2, NULL} + }, + { /* tcm 3 scan, scan order 0 out0; scan order 1 out1; + * scan order 2 out2 + */ + "tcmscan:3:0:1:2", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2, + LED_C0, LED_C1, LED_C2, NULL} + }, + { /* tcm 3 scan, scan order 0 out0; scan order 1 out1; + * scan order 2 out3 + */ + "tcmscan:3:0:1:3", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2, + LED_D0, LED_D1, LED_D2, NULL} + }, + { /* tcm 3 scan, scan order 0 out0; scan order 1 out2; + * scan order 2 out3 + */ + "tcmscan:3:0:2:3", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_C0, LED_C1, LED_C2, + LED_D0, LED_D1, LED_D2, NULL} + }, + { /* tcm 4 scan, scan order 0 out0; scan order 1 out1; + * scan order 2 out2; scan order 3 out3 + */ + "tcmscan:4:0:1:2:3", + (const char *[]){LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, LED_B2, + LED_C0, LED_C1, LED_C2, LED_D0, LED_D1, LED_D2, NULL} + }, + { /* mix 1 scan, direct connect out0; scan order 0 out1 */ + "mixscan:1:0:1", + (const char *[]){LED0, LED_B0, LED_B1, NULL} + }, + { /* mix 1 scan, direct connect out0; scan order 0 out2 */ + "mixscan:1:0:2", + (const char *[]){LED0, LED_C0, LED_C2, NULL} + }, + { /* mix 1 scan, direct connect out0; scan order 0 out3 */ + "mixscan:1:0:3", + (const char *[]){LED0, LED_D1, LED_D2, NULL} + }, + { /* mix 1 scan, direct connect out1; scan order 0 out0 */ + "mixscan:1:1:0", + (const char *[]){LED1, LED_A1, LED_A2, NULL} + }, + { /* mix 1 scan, direct connect out1; scan order 0 out2 */ + "mixscan:1:1:2", + (const char *[]){LED1, LED_C0, LED_C1, NULL} + }, + { /* mix 1 scan, direct connect out1; scan order 0 out3 */ + "mixscan:1:1:3", + (const char *[]){LED1, LED_D0, LED_D2, NULL} + }, + { /* mix 1 scan, direct connect out2; scan order 0 out0 */ + "mixscan:1:2:0", + (const char *[]){LED2, LED_A0, LED_A2, NULL} + }, + { /* mix 1 scan, direct connect out2; scan order 0 out1 */ + "mixscan:1:2:1", + (const char *[]){LED2, LED_B1, LED_B2, NULL} + }, + { /* mix 1 scan, direct connect out2; scan order 0 out3 */ + "mixscan:1:2:3", + (const char *[]){LED2, LED_D0, LED_D1, NULL} + }, + { /* mix 1 scan, direct connect out3; scan order 0 out0 */ + "mixscan:1:3:0", + (const char *[]){LED3, LED_A0, LED_A1, NULL} + }, + { /* mix 1 scan, direct connect out3; scan order 0 out1 */ + "mixscan:1:3:1", + (const char *[]){LED3, LED_B0, LED_B2, NULL} + }, + { /* mix 1 scan, direct connect out3; scan order 0 out2 */ + "mixscan:1:3:2", + (const char *[]){LED3, LED_C1, LED_C2, NULL} + }, + { /* mix 2 scan, direct connect out0; scan order 0 out1; + * scan order 1 out2 + */ + "mixscan:2:0:1:2", + (const char *[]){LED0, LED_B0, LED_B1, LED_C0, LED_C2, NULL} + }, + { /* mix 2 scan, direct connect out0; scan order 0 out1; + * scan order 1 out3 + */ + "mixscan:2:0:1:3", + (const char *[]){LED0, LED_B0, LED_B1, LED_D1, LED_D2, NULL} + }, + { /* mix 2 scan, direct connect out0; scan order 0 out2; + * scan order 1 out3 + */ + "mixscan:2:0:2:3", + (const char *[]){LED0, LED_C0, LED_C2, LED_D1, LED_D2, NULL} + }, + { /* mix 2 scan, direct connect out1; scan order 0 out0; + * scan order 1 out2 + */ + "mixscan:2:1:0:2", + (const char *[]){LED1, LED_A1, LED_A2, LED_C0, LED_C1, NULL} + }, + { /* mix 2 scan, direct connect out1; scan order 0 out0; + * scan order 1 out3 + */ + "mixscan:2:1:0:3", + (const char *[]){LED1, LED_A1, LED_A2, LED_D0, LED_D2, NULL} + }, + { /* mix 2 scan, direct connect out1; scan order 0 out2; + * scan order 1 out3 + */ + "mixscan:2:1:2:3", + (const char *[]){LED1, LED_C0, LED_C1, LED_D0, LED_D2, NULL} + }, + { /* mix 2 scan, direct connect out2; scan order 0 out0; + * scan order 1 out1 + */ + "mixscan:2:2:0:1", + (const char *[]){LED2, LED_A0, LED_A2, LED_B1, LED_B2, NULL} + }, + { /* mix 2 scan, direct connect out2; scan order 0 out0; + * scan order 1 out3 + */ + "mixscan:2:2:0:3", + (const char *[]){LED2, LED_A0, LED_A2, LED_D0, LED_D1, NULL} + }, + { /* mix 2 scan, direct connect out2; scan order 0 out1; + * scan order 1 out3 + */ + "mixscan:2:2:1:3", + (const char *[]){LED2, LED_B1, LED_B2, LED_D0, LED_D1, NULL} + }, + { /* mix 2 scan, direct connect out3; scan order 0 out0; + * scan order 1 out1 + */ + "mixscan:2:3:0:1", + (const char *[]){LED3, LED_A0, LED_A1, LED_B0, LED_B2, NULL} + }, + { /* mix 2 scan, direct connect out3; scan order 0 out0; + * scan order 1 out2 + */ + "mixscan:2:3:0:2", + (const char *[]){LED3, LED_A0, LED_A1, LED_C1, LED_C2, NULL} + }, + { /* mix 2 scan, direct connect out3; scan order 0 out1; + * scan order 1 out2 + */ + "mixscan:2:3:1:2", + (const char *[]){LED3, LED_B0, LED_B2, LED_C1, LED_C2, NULL} + }, + { /* mix 3 scan, direct connect out0; scan order 0 out1; + * scan order 1 out2; scan order 2 out3 + */ + "mixscan:3:0:1:2:3", + (const char *[]){LED0, LED_B0, LED_B1, LED_C0, LED_C2, LED_D1, + LED_D2, NULL} + }, + { /* mix 3 scan, direct connect out1; scan order 0 out0; + * scan order 1 out2; scan order 2 out3 + */ + "mixscan:3:1:0:2:3", + (const char *[]){LED1, LED_A1, LED_A2, LED_C0, LED_C1, LED_D0, + LED_D2, NULL} + }, + { /* mix 3 scan, direct connect out2; scan order 0 out0; + * scan order 1 out1; scan order 2 out3 + */ + "mixscan:3:2:0:1:3", + (const char *[]){LED2, LED_A0, LED_A2, LED_B1, LED_B2, LED_D0, + LED_D1, NULL} + }, + { /* mix 3 scan, direct connect out3; scan order 0 out0; + * scan order 1 out1; scan order 2 out2 + */ + "mixscan:3:3:0:1:2", + (const char *[]){LED3, LED_A0, LED_A1, LED_B0, LED_B2, LED_C1, + LED_C2, NULL} + } +}; + +static const char *led_name_array[MAX_LEDS] =3D { + LED0, LED1, LED2, LED3, LED_A0, LED_A1, LED_A2, LED_B0, LED_B1, + LED_B2, LED_C0, LED_C1, LED_C2, LED_D0, LED_D1, LED_D2 +}; + +static u16 anim_base_addr_array[MAX_LEDS] =3D { + LED0_AUTO_BASE_ADRR, LED1_AUTO_BASE_ADRR, LED2_AUTO_BASE_ADRR, + LED3_AUTO_BASE_ADRR, LED_A0_AUTO_BASE_ADRR, LED_A1_AUTO_BASE_ADRR, + LED_A2_AUTO_BASE_ADRR, LED_B0_AUTO_BASE_ADRR, LED_B1_AUTO_BASE_ADRR, + LED_B2_AUTO_BASE_ADRR, LED_C0_AUTO_BASE_ADRR, LED_C1_AUTO_BASE_ADRR, + LED_C2_AUTO_BASE_ADRR, LED_D0_AUTO_BASE_ADRR, LED_D1_AUTO_BASE_ADRR, + LED_D2_AUTO_BASE_ADRR +}; + +static const char *time_name_array[MAX_TIME] =3D { + TIME0, TIME1, TIME2, TIME3, TIME4, TIME5, TIME6, TIME7, TIME8, + TIME9, TIME10, TIME11, TIME12, TIME13, TIME14, TIME15 +}; + +static const char *led_playback_time_arr[MAX_TIME] =3D { + "0 time", "1 time", "2 times", "3 times", "4 times", "5 times", + "6 times", "7 times", "8 times", "9 times", "10 times", "11 times", + "12 times", "13 times", "14 times", "infinite times" +}; + +static const char *aeu_name_array[MAX_AEU] =3D {AEU1, AEU2, AEU3}; + +static const struct lp5812_specific_regs regs =3D { + .enable_reg =3D CHIP_EN_REG, + .reset_reg =3D RESET_REG, + .update_cmd_reg =3D CMD_UPDATE_REG, + .start_cmd_reg =3D CMD_START_REG, + .stop_cmd_reg =3D CMD_STOP_REG, + .pause_cmd_reg =3D CMD_PAUSE_REG, + .continue_cmd_reg =3D CMD_CONTINUE_REG, + .fault_clear_reg =3D FAULT_CLEAR_REG, + .tsd_config_status_reg =3D TSD_CONFIG_STAT_REG, +}; + +static void led_kobj_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static void aeu_kobj_release(struct kobject *kobj) +{ + kfree(kobj); +} + +static const struct kobj_type led_ktype =3D { + .release =3D led_kobj_release, + .sysfs_ops =3D &kobj_sysfs_ops, +}; + +static const struct kobj_type aeu_ktype =3D { + .release =3D aeu_kobj_release, + .sysfs_ops =3D &kobj_sysfs_ops, +}; + +static int lp5812_write(struct lp5812_chip *chip, u16 reg, u8 val) +{ + int ret; + u8 extracted_bits; /* save 9th and 8th bit of reg address */ + struct i2c_msg msg; + u8 buf[2] =3D {(u8)(reg & 0xFF), val}; + + extracted_bits =3D (reg >> 8) & 0x03; + msg.addr =3D (chip->i2c_cl->addr << 2) | extracted_bits; + msg.flags =3D 0; + msg.len =3D sizeof(buf); + msg.buf =3D buf; + + ret =3D i2c_transfer(chip->i2c_cl->adapter, &msg, 1); + if (ret !=3D 1) { + dev_err(chip->dev, "i2c write error, register 0x%02X, ret=3D%d\n", reg, = ret); + ret =3D ret < 0 ? ret : -EIO; + } else { + ret =3D 0; + } + + return ret; +} + +static int lp5812_read(struct lp5812_chip *chip, u16 reg, u8 *val) +{ + int ret; + u8 ret_val; /* lp5812_chip return value */ + u8 extracted_bits; /* save 9th and 8th bit of reg address */ + u8 converted_reg; /* extracted 8bit from reg */ + struct i2c_msg msgs[2]; + + extracted_bits =3D (reg >> 8) & 0x03; + converted_reg =3D (u8)(reg & 0xFF); + + msgs[0].addr =3D (chip->i2c_cl->addr << 2) | extracted_bits; + msgs[0].flags =3D 0; + msgs[0].len =3D 1; + msgs[0].buf =3D &converted_reg; + + msgs[1].addr =3D (chip->i2c_cl->addr << 2) | extracted_bits; + msgs[1].flags =3D I2C_M_RD; + msgs[1].len =3D 1; + msgs[1].buf =3D &ret_val; + + ret =3D i2c_transfer(chip->i2c_cl->adapter, msgs, 2); + if (ret !=3D 2) { + dev_err(chip->dev, "Read register 0x%02X error, ret=3D%d\n", reg, ret); + *val =3D 0; + ret =3D ret < 0 ? ret : -EIO; + } else { + *val =3D ret_val; + ret =3D 0; + } + + return ret; +} + +static int lp5812_read_tsd_config_status(struct lp5812_chip *chip, u8 *reg= _val) +{ + int ret; + + if (!reg_val) + return -1; + + ret =3D lp5812_read(chip, chip->regs->tsd_config_status_reg, reg_val); + + return ret; +} + +static int lp5812_update_regs_config(struct lp5812_chip *chip) +{ + int ret; + u8 reg_val; /* save register value */ + + /* Send update command to update config setting */ + ret =3D lp5812_write(chip, chip->regs->update_cmd_reg, UPDATE_CMD_VAL); + if (ret) + return ret; + /* check if the configuration is proper */ + ret =3D lp5812_read_tsd_config_status(chip, ®_val); + if (ret =3D=3D 0) + return (int)(reg_val & 0x01); + + return ret; +} + +static int lp5812_read_lod_status(struct lp5812_chip *chip, int led_number= , u8 *val) +{ + int ret; + u16 reg; + u8 reg_val; + + if (!val) + return -1; + + if (led_number < 0x8) + reg =3D LOD_STAT_1_REG; + else + reg =3D LOD_STAT_2_REG; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *val =3D (reg_val & (1 << (led_number % 8))) ? 1 : 0; + + return ret; +} + +static int lp5812_read_lsd_status(struct lp5812_chip *chip, int led_number= , u8 *val) +{ + int ret; + u16 reg; + u8 reg_val; + + if (!val) + return -1; + + if (led_number < 0x8) + reg =3D LSD_STAT_1_REG; + else + reg =3D LSD_STAT_2_REG; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *val =3D (reg_val & (1 << (led_number % 8))) ? 1 : 0; + + return ret; +} + +static int lp5812_read_auto_pwm_value(struct lp5812_chip *chip, int led_nu= mber, + u8 *val) +{ + int ret; + u16 reg; + u8 reg_val; + + reg =3D AUTO_PWM_BASE_ADDR + led_number; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *val =3D reg_val; + + return ret; +} + +static int lp5812_read_aep_status(struct lp5812_chip *chip, int led_number= , u8 *val) +{ + int ret; + u16 reg; + u8 reg_val; + + switch (led_number / 2) { + case 0: + reg =3D AEP_STATUS_0_REG; // LED_0 and LED_1 + break; + case 1: + reg =3D AEP_STATUS_1_REG; // LED_2 and LED_3 + break; + case 2: + reg =3D AEP_STATUS_2_REG; // LED_A0 and LED_A1 + break; + case 3: + reg =3D AEP_STATUS_3_REG; // LED_A2 and LED_B0 + break; + case 4: + reg =3D AEP_STATUS_4_REG; // LED_B1 and LED_B2 + break; + case 5: + reg =3D AEP_STATUS_5_REG; // LED_C0 and LED_C1 + break; + case 6: + reg =3D AEP_STATUS_6_REG; // LED_C2 and LED_D0 + break; + case 7: + reg =3D AEP_STATUS_7_REG; // LED_D1 and LED_D2 + break; + default: + return -EINVAL; + } + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *val =3D (led_number % 2) ? ((reg_val >> 3) & 0x07) : (reg_val & 0x07); + + return ret; +} + +static int lp5812_enable_disable(struct lp5812_chip *chip, int enable) +{ + return lp5812_write(chip, chip->regs->enable_reg, (u8)enable); +} + +static int lp5812_reset(struct lp5812_chip *chip) +{ + return lp5812_write(chip, chip->regs->reset_reg, RESET_REG_VAL); +} + +static int lp5812_fault_clear(struct lp5812_chip *chip, u8 value) +{ + u8 reg_val; + + if (value =3D=3D 0) + reg_val =3D LOD_CLEAR_VAL; + else if (value =3D=3D 1) + reg_val =3D LSD_CLEAR_VAL; + else if (value =3D=3D 2) + reg_val =3D TSD_CLEAR_VAL; + else if (value =3D=3D 3) + reg_val =3D FAULT_CLEAR_ALL; + else + return -EINVAL; + + return lp5812_write(chip, chip->regs->fault_clear_reg, reg_val); +} + +static int lp5812_device_command(struct lp5812_chip *chip, enum device_com= mand command) +{ + switch (command) { + case UPDATE: + return lp5812_write(chip, chip->regs->update_cmd_reg, + UPDATE_CMD_VAL); + case START: + return lp5812_write(chip, chip->regs->start_cmd_reg, + START_CMD_VAL); + case STOP: + return lp5812_write(chip, chip->regs->stop_cmd_reg, + STOP_CMD_VAL); + case PAUSE: + return lp5812_write(chip, chip->regs->pause_cmd_reg, + PAUSE_CMD_VAL); + case CONTINUE: + return lp5812_write(chip, chip->regs->continue_cmd_reg, + CONTINUE_CMD_VAL); + default: + return -EINVAL; + } +} + +static int lp5812_set_pwm_dimming_scale(struct lp5812_chip *chip, int led_= number, + enum pwm_dimming_scale scale) +{ + int ret; + u16 reg; + u8 reg_val; + + if (led_number <=3D 7) + reg =3D (u16)DEV_CONFIG5; + else + reg =3D (u16)DEV_CONFIG6; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + if (scale =3D=3D LINEAR) { + reg_val &=3D ~(1 << (led_number % 8)); + ret =3D lp5812_write(chip, reg, reg_val); + if (ret) + return ret; + } else { + reg_val |=3D (1 << (led_number % 8)); + ret =3D lp5812_write(chip, reg, reg_val); + if (ret) + return ret; + } + + ret =3D lp5812_update_regs_config(chip); + + return ret; +} + +static int lp5812_get_pwm_dimming_scale(struct lp5812_chip *chip, + int led_number, enum pwm_dimming_scale *scale) +{ + int ret; + u16 reg; + u8 reg_val; + + if (led_number < 0x8) + reg =3D DEV_CONFIG5; + else + reg =3D DEV_CONFIG6; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *scale =3D (reg_val & (1 << (led_number % 8))) ? EXPONENTIAL : LINEAR; + + return 0; +} + +static int lp5812_set_phase_align(struct lp5812_chip *chip, int led_number, + int phase_align_val) +{ + int ret; + int bit_pos; + u16 reg; + u8 reg_val; + + reg =3D DEV_CONFIG7 + (led_number / 4); + bit_pos =3D (led_number % 4) * 2; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + reg_val |=3D (phase_align_val << bit_pos); + ret =3D lp5812_write(chip, reg, reg_val); + if (ret) + return ret; + ret =3D lp5812_update_regs_config(chip); + + return ret; +} + +static int lp5812_get_phase_align(struct lp5812_chip *chip, int led_number, + int *phase_align_val) +{ + int ret; + int bit_pos; + u16 reg; + u8 reg_val; + + reg =3D DEV_CONFIG7 + (led_number / 4); + bit_pos =3D (led_number % 4) * 2; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *phase_align_val =3D (reg_val >> bit_pos) & 0x03; + + return ret; +} + +static int lp5812_get_led_mode(struct lp5812_chip *chip, + int led_number, enum control_mode *mode) +{ + int ret; + u16 reg; + u8 reg_val; + + if (led_number < 0x8) + reg =3D DEV_CONFIG3; + else + reg =3D DEV_CONFIG4; + + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + + *mode =3D (reg_val & (1 << (led_number % 8))) ? AUTONOMOUS : MANUAL; + + return 0; +} + +static int lp5812_set_led_mode(struct lp5812_chip *chip, int led_number, + enum control_mode mode) +{ + int ret; + u16 reg; + u8 reg_val; + + if (led_number <=3D 7) + reg =3D (u16)DEV_CONFIG3; + else + reg =3D (u16)DEV_CONFIG4; + ret =3D lp5812_read(chip, reg, ®_val); + if (ret) + return ret; + if (mode =3D=3D MANUAL) { + reg_val &=3D ~(1 << (led_number % 8)); + ret =3D lp5812_write(chip, reg, reg_val); + if (ret) + return ret; + } else { + reg_val |=3D (1 << (led_number % 8)); + ret =3D lp5812_write(chip, reg, reg_val); + if (ret) + return ret; + } + + ret =3D lp5812_update_regs_config(chip); + + return ret; +} + +static int lp5812_manual_dc_pwm_control(struct lp5812_chip *chip, + int led_number, u8 val, enum dimming_type dimming_type) +{ + int ret; + u16 led_base_reg; + + if (dimming_type =3D=3D ANALOG) + led_base_reg =3D (u16)MANUAL_DC_LED_0_REG; + else + led_base_reg =3D (u16)MANUAL_PWM_LED_0_REG; + ret =3D lp5812_write(chip, led_base_reg + led_number, val); + + return ret; +} + +static int lp5812_manual_dc_pwm_read(struct lp5812_chip *chip, + int led_number, u8 *val, enum dimming_type dimming_type) +{ + int ret; + u16 led_base_reg; + + if (dimming_type =3D=3D ANALOG) + led_base_reg =3D (u16)MANUAL_DC_LED_0_REG; + else + led_base_reg =3D (u16)MANUAL_PWM_LED_0_REG; + ret =3D lp5812_read(chip, led_base_reg + led_number, val); + + return ret; +} + +static int lp5812_autonomous_dc_pwm_control(struct lp5812_chip *chip, + int led_number, u8 val, enum dimming_type dimming_type) +{ + int ret; + u16 led_base_reg; + + led_base_reg =3D (u16)AUTO_DC_LED_0_REG; + ret =3D lp5812_write(chip, led_base_reg + led_number, val); + + return ret; +} + +static int lp5812_autonomous_dc_pwm_read(struct lp5812_chip *chip, + int led_number, u8 *val, enum dimming_type dimming_type) +{ + int ret; + u16 led_base_reg; + + led_base_reg =3D (u16)AUTO_DC_LED_0_REG; + ret =3D lp5812_read(chip, led_base_reg + led_number, val); + + return ret; +} + +static int lp5812_disable_all_leds(struct lp5812_chip *chip) +{ + int ret; + + ret =3D lp5812_write(chip, (u16)LED_ENABLE_1_REG, 0x00); + if (ret) + return ret; + ret =3D lp5812_write(chip, (u16)LED_ENABLE_2_REG, 0x00); + if (ret) + return ret; + + return ret; +} + +static int lp5812_get_drive_mode_scan_order(struct lp5812_chip *chip) +{ + u8 val; + int ret; + + /* get led mode */ + ret =3D lp5812_read(chip, (u16)DEV_CONFIG1, &val); + if (ret) + return ret; + chip->u_drive_mode.drive_mode_val =3D val; + + /* get scan order */ + ret =3D lp5812_read(chip, (u16)DEV_CONFIG2, &val); + if (ret) + return ret; + chip->u_scan_order.scan_order_val =3D val; + + return ret; +} + +static int lp5812_set_drive_mode_scan_order(struct lp5812_chip *chip) +{ + u8 val; + int ret; + + /* Set led mode */ + val =3D chip->u_drive_mode.drive_mode_val; + ret =3D lp5812_write(chip, (u16)DEV_CONFIG1, val); + if (ret) + return ret; + + /* Setup scan order */ + val =3D chip->u_scan_order.scan_order_val; + ret =3D lp5812_write(chip, (u16)DEV_CONFIG2, val); + if (ret) + return ret; + + return ret; +} + +static int lp5812_initialize(struct lp5812_chip *chip) +{ + int ret; + + /* wait for 1 ms */ + usleep_range(1000, 1100); + + /* enable the lp5812 */ + ret =3D lp5812_enable_disable(chip, 1); + if (ret) { + dev_err(chip->dev, "lp5812_enable_disable failed\n"); + return ret; + } + ret =3D lp5812_set_drive_mode_scan_order(chip); + if (ret) { + dev_err(chip->dev, "lp5812_set_drive_mode_scan_order failed\n"); + return ret; + } + + /* Set lsd_threshold =3D 3h to avoid incorrect LSD detection */ + ret =3D lp5812_write(chip, (u16)DEV_CONFIG12, 0x0B); + if (ret) { + dev_err(chip->dev, "write 0x0B to DEV_CONFIG12 failed\n"); + return ret; + } + + /* Send update command to complete configuration settings */ + ret =3D lp5812_update_regs_config(chip); + if (ret) { + dev_err(chip->dev, "lp5812_update_regs_config failed\n"); + return ret; + } + + /* Enable LED_A0 for testing */ + ret =3D lp5812_write(chip, (u16)LED_ENABLE_1_REG, 0x20); + if (ret) { + dev_err(chip->dev, "write 0x10 to LED_ENABLE_1_REG failed\n"); + return ret; + } + /* set max DC current for LED_A0 */ + ret =3D lp5812_write(chip, (u16)0x35, 0x80); + if (ret) + dev_err(chip->dev, "set max DC current for LED_A0 failed\n"); + + /* set 100% pwm cycle for LED_A0 */ + ret =3D lp5812_write(chip, (u16)0x45, 0x80); + if (ret) + dev_err(chip->dev, "set 100 percent pwm cycle for LED_A0 failed\n"); + + return ret; +} + +static int led_set_autonomous_animation_config(struct lp5812_led *led) +{ + int ret; + u16 reg; + struct lp5812_chip *chip =3D led->priv; + + /* Set start/end pause */ + reg =3D led->anim_base_addr + AUTO_PAUSE; + ret =3D lp5812_write(chip, reg, led->start_stop_pause_time.time_val); + if (ret) + return ret; + + /* Set led playback and AEU selection */ + reg =3D led->anim_base_addr + AUTO_PLAYBACK; + ret =3D lp5812_write(chip, reg, led->led_playback.led_playback_val); + if (ret) + return ret; + + return 0; +} + +static int led_get_autonomous_animation_config(struct lp5812_led *led) +{ + int ret; + u16 reg; + struct lp5812_chip *chip =3D led->priv; + + /* Get start/end pause value */ + reg =3D led->anim_base_addr + AUTO_PAUSE; + ret =3D lp5812_read(chip, reg, &led->start_stop_pause_time.time_val); + if (ret) + return ret; + + /* Get led playback and AEU selection values */ + reg =3D led->anim_base_addr + AUTO_PLAYBACK; + ret =3D lp5812_read(chip, reg, &led->led_playback.led_playback_val); + if (ret) + return ret; + + return 0; +} + +static u16 get_aeu_pwm_register(struct anim_engine_unit *aeu, + enum pwm_slope_time_num pwm_num) +{ + u16 reg; + struct lp5812_led *led =3D aeu->led; + + switch (pwm_num) { + case PWM1: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_PWM_1; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_PWM_1; + else + reg =3D led->anim_base_addr + AEU3_PWM_1; + break; + case PWM2: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_PWM_2; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_PWM_2; + else + reg =3D led->anim_base_addr + AEU3_PWM_2; + break; + case PWM3: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_PWM_3; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_PWM_3; + else + reg =3D led->anim_base_addr + AEU3_PWM_3; + break; + case PWM4: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_PWM_4; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_PWM_4; + else + reg =3D led->anim_base_addr + AEU3_PWM_4; + break; + case PWM5: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_PWM_5; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_PWM_5; + else + reg =3D led->anim_base_addr + AEU3_PWM_5; + break; + default: + reg =3D led->anim_base_addr; + break; + } + + return reg; +} + +static u16 get_aeu_slope_time_register(struct anim_engine_unit *aeu, + enum pwm_slope_time_num slope_time_num) +{ + u16 reg; + struct lp5812_led *led =3D aeu->led; + + switch (slope_time_num) { + case SLOPE_T1: + case SLOPE_T2: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_T12; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_T12; + else + reg =3D led->anim_base_addr + AEU3_T12; + break; + case SLOPE_T3: + case SLOPE_T4: + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_T34; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_T34; + else + reg =3D led->anim_base_addr + AEU3_T34; + break; + default: + reg =3D led->anim_base_addr; + break; + } + + return reg; +} + +static u16 get_aeu_playback_time_register(struct anim_engine_unit *aeu) +{ + u16 reg; + struct lp5812_led *led =3D aeu->led; + + if (aeu->aeu_number =3D=3D 1) + reg =3D led->anim_base_addr + AEU1_PLAYBACK; + else if (aeu->aeu_number =3D=3D 2) + reg =3D led->anim_base_addr + AEU2_PLAYBACK; + else + reg =3D led->anim_base_addr + AEU3_PLAYBACK; + + return reg; +} + +static int led_aeu_pwm_set_val(struct anim_engine_unit *aeu, u8 val, + enum pwm_slope_time_num pwm_num) +{ + int ret; + u16 reg; + struct lp5812_led *led =3D aeu->led; + struct lp5812_chip *chip =3D led->priv; + + reg =3D get_aeu_pwm_register(aeu, pwm_num); + ret =3D lp5812_write(chip, reg, val); + + return ret; +} + +static int led_aeu_pwm_get_val(struct anim_engine_unit *aeu, u8 *val, + enum pwm_slope_time_num pwm_num) +{ + int ret; + u16 reg; + struct lp5812_led *led =3D aeu->led; + struct lp5812_chip *chip =3D led->priv; + + reg =3D get_aeu_pwm_register(aeu, pwm_num); + ret =3D lp5812_read(chip, reg, val); + + return ret; +} + +static int led_aeu_slope_time_set_val(struct anim_engine_unit *aeu, u8 val, + enum pwm_slope_time_num slope_time_num) +{ + int ret; + u16 reg; + union time slope_time_val; + struct lp5812_led *led =3D aeu->led; + struct lp5812_chip *chip =3D led->priv; + + reg =3D get_aeu_slope_time_register(aeu, slope_time_num); + + /* get original value of slope time */ + ret =3D lp5812_read(chip, reg, &slope_time_val.time_val); + if (ret) + return ret; + + /* Update new value for slope time*/ + if (slope_time_num =3D=3D SLOPE_T1 || slope_time_num =3D=3D SLOPE_T3) + slope_time_val.s_time.first =3D val; + if (slope_time_num =3D=3D SLOPE_T2 || slope_time_num =3D=3D SLOPE_T4) + slope_time_val.s_time.second =3D val; + + /* Save updated value to hardware */ + ret =3D lp5812_write(chip, reg, slope_time_val.time_val); + + return ret; +} + +static int led_aeu_slope_time_get_val(struct anim_engine_unit *aeu, u8 *va= l, + enum pwm_slope_time_num slope_time_num) +{ + int ret; + u16 reg; + union time slope_time_val; + struct lp5812_led *led =3D aeu->led; + struct lp5812_chip *chip =3D led->priv; + + reg =3D get_aeu_slope_time_register(aeu, slope_time_num); + /* get slope time value */ + ret =3D lp5812_read(chip, reg, &slope_time_val.time_val); + if (ret) + return ret; + + if (slope_time_num =3D=3D SLOPE_T1 || slope_time_num =3D=3D SLOPE_T3) + *val =3D slope_time_val.s_time.first; + + if (slope_time_num =3D=3D SLOPE_T2 || slope_time_num =3D=3D SLOPE_T4) + *val =3D slope_time_val.s_time.second; + + return ret; +} + +static int led_aeu_playback_time_set_val(struct anim_engine_unit *aeu, u8 = val) +{ + int ret; + u16 reg; + struct lp5812_led *led =3D aeu->led; + struct lp5812_chip *chip =3D led->priv; + + reg =3D get_aeu_playback_time_register(aeu); + ret =3D lp5812_write(chip, reg, val); + + return ret; +} + +static int led_aeu_playback_time_get_val(struct anim_engine_unit *aeu, u8 = *val) +{ + int ret; + u16 reg; + struct lp5812_led *led =3D aeu->led; + struct lp5812_chip *chip =3D led->priv; + + reg =3D get_aeu_playback_time_register(aeu); + ret =3D lp5812_read(chip, reg, val); + + return ret; +} + +static int aeu_create_sysfs_group(struct anim_engine_unit *aeu) +{ + int ret; + + ret =3D kobject_add(&aeu->kobj, &aeu->led->kobj, "%s", + aeu->aeu_name); + if (ret) + return ret; + ret =3D sysfs_create_group(&aeu->kobj, &aeu->attr_group); + aeu->enabled =3D 1; + + return ret; +} + +static void aeu_remove_sysfs_group(struct anim_engine_unit *aeu) +{ + aeu->enabled =3D 0; + sysfs_remove_group(&aeu->kobj, &aeu->attr_group); + kobject_del(&aeu->kobj); +} + +/* Function to remove multiple sysfs group of AEU in one led */ +static void aeu_remove_multi_sysfs_groups(struct lp5812_led *led) +{ + int i; + + for (i =3D 0; i < MAX_AEU; i++) { + if (led->aeu[i].enabled) + aeu_remove_sysfs_group(&led->aeu[i]); + } +} + +/* + * Function to create multi sysfs group for specific led. it will + * check the mode of led and AEU number selection to create + * corresponding AEU interfaces + */ +static int aeu_create_multi_sysfs_groups(struct lp5812_led *led) +{ + int i; + int aeu_select; + enum control_mode mode; + struct lp5812_chip *chip =3D led->priv; + + if (lp5812_get_led_mode(chip, led->led_number, &mode)) + return -EIO; + if (mode =3D=3D AUTONOMOUS) { + if (led_get_autonomous_animation_config(led)) + return -EIO; + aeu_select =3D led->led_playback.s_led_playback.aeu_selection; + if (aeu_select =3D=3D 3) + aeu_select =3D 2; + for (i =3D 0; i < aeu_select + 1; i++) + aeu_create_sysfs_group(&led->aeu[i]); + } + + return 0; +} + +static int lp5812_create_sysfs_group(struct lp5812_led *led) +{ + int ret; + + ret =3D kobject_add(&led->kobj, &led->priv->dev->kobj, "%s", + led->led_name); + if (ret) + return ret; + ret =3D sysfs_create_group(&led->kobj, &led->attr_group); + if (ret) + return ret; + led->is_sysfs_created =3D 1; + + return ret; +} + +static void lp5812_remove_sysfs_group(struct lp5812_led *led) +{ + int i; + + /* remove sysfs group of AEU */ + for (i =3D 0; i < MAX_AEU; i++) { + if (led->aeu[i].enabled) + aeu_remove_sysfs_group(&led->aeu[i]); + } + led->is_sysfs_created =3D 0; + sysfs_remove_group(&led->kobj, &led->attr_group); + kobject_del(&led->kobj); +} + +static ssize_t device_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int enable; + int ret; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + ret =3D kstrtoint(buf, 0, &enable); + if (ret) + return ret; + + if (enable !=3D 0 && enable !=3D 1) + return -EINVAL; + + /* set to hardware */ + mutex_lock(&chip->lock); + ret =3D lp5812_enable_disable(chip, enable); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return len; +} + +static ssize_t device_enable_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 enable; + int ret; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + mutex_lock(&chip->lock); + ret =3D lp5812_read(chip, chip->regs->enable_reg, &enable); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", enable); +} + +static ssize_t device_command_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + enum device_command cmd; + + if (sysfs_streq(buf, "update")) + cmd =3D UPDATE; + else if (sysfs_streq(buf, "start")) + cmd =3D START; + else if (sysfs_streq(buf, "stop")) + cmd =3D STOP; + else if (sysfs_streq(buf, "pause")) + cmd =3D PAUSE; + else if (sysfs_streq(buf, "continue")) + cmd =3D CONTINUE; + else + return -EINVAL; + + mutex_lock(&chip->lock); + lp5812_device_command(chip, cmd); + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t sw_reset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int reset; + int ret; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + ret =3D kstrtoint(buf, 0, &reset); + if (ret) + return ret; + + if (reset !=3D 1) + return -EINVAL; + + /* reset hardware */ + mutex_lock(&chip->lock); + lp5812_reset(chip); + msleep(1000); + /* set back manual mode as default for all LEDs */ + lp5812_write(chip, (u16)DEV_CONFIG3, 0x00); + lp5812_write(chip, (u16)DEV_CONFIG4, 0x00); + lp5812_update_regs_config(chip); + mutex_unlock(&chip->lock); + + /* Update sysfs based on default mode when hardware reseted*/ + ret =3D lp5812_init_dev_config(chip, "direct_mode", 1); + if (ret) { + dev_err(dev, "%s: lp5812_init_dev_config failed\n", + __func__); + return ret; + } + + return len; +} + +static ssize_t fault_clear_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int fault_clear; + int ret; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + ret =3D kstrtoint(buf, 0, &fault_clear); + if (ret) + return ret; + + if (fault_clear < 0 || fault_clear > 3) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D lp5812_fault_clear(chip, fault_clear); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return len; +} + +static void lp5812_create_sysfs_via_led_name(struct lp5812_chip *chip, + const char *led_name) +{ + int i; + + for (i =3D 0; i < MAX_LEDS; i++) { + if (sysfs_streq(led_name, chip->leds[i].led_name)) { + lp5812_create_sysfs_group(&chip->leds[i]); + + /* + * create AEU interface if current led mode is + * autonomous + */ + aeu_create_multi_sysfs_groups(&chip->leds[i]); + } + } +} + +static void set_mix_sel_led(struct lp5812_chip *chip, int mix_sel_led) +{ + chip->u_drive_mode.s_drive_mode.mix_sel_led_0 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_1 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_2 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_3 =3D 0; + + if (mix_sel_led =3D=3D 0) + chip->u_drive_mode.s_drive_mode.mix_sel_led_0 =3D 1; + else if (mix_sel_led =3D=3D 1) + chip->u_drive_mode.s_drive_mode.mix_sel_led_1 =3D 1; + else if (mix_sel_led =3D=3D 2) + chip->u_drive_mode.s_drive_mode.mix_sel_led_2 =3D 1; + else if (mix_sel_led =3D=3D 3) + chip->u_drive_mode.s_drive_mode.mix_sel_led_3 =3D 1; +} + +static void parse_drive_mode(struct lp5812_chip *chip, char *str) +{ + int ret; + char *sub_str; + int tcm_scan_num, mix_scan_num, mix_sel_led; + int scan_oder0, scan_oder1, scan_oder2, scan_oder3; + + sub_str =3D strsep(&str, ":"); + if (sysfs_streq(sub_str, "direct_mode")) { + chip->u_drive_mode.s_drive_mode.led_mode =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_0 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_1 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_2 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_3 =3D 0; + } + if (sysfs_streq(sub_str, "tcmscan")) { + chip->u_drive_mode.s_drive_mode.mix_sel_led_0 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_1 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_2 =3D 0; + chip->u_drive_mode.s_drive_mode.mix_sel_led_3 =3D 0; + + /* Get tcm scan number */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &tcm_scan_num); + if (ret) + return; + chip->u_drive_mode.s_drive_mode.led_mode =3D tcm_scan_num; + switch (tcm_scan_num) { + case TCM_1_SCAN: + /* Get scan_oder0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + break; + case TCM_2_SCAN: + /* Get scan_order0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + /* Get scan_order1 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder1); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + chip->u_scan_order.s_scan_order.scan_order_1 =3D + scan_oder1; + break; + case TCM_3_SCAN: + /* Get scan_order0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + /* Get scan_order1 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder1); + if (ret) + return; + /* Get scan_order2 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder2); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + chip->u_scan_order.s_scan_order.scan_order_1 =3D + scan_oder1; + chip->u_scan_order.s_scan_order.scan_order_2 =3D + scan_oder2; + break; + case TCM_4_SCAN: + /* Get scan_order0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + /* Get scan_order1 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder1); + if (ret) + return; + /* Get scan_order2 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder2); + if (ret) + return; + /* Get scan_order3 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder3); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + chip->u_scan_order.s_scan_order.scan_order_1 =3D + scan_oder1; + chip->u_scan_order.s_scan_order.scan_order_2 =3D + scan_oder2; + chip->u_scan_order.s_scan_order.scan_order_3 =3D + scan_oder3; + break; + default: + break; + } + } + if (sysfs_streq(sub_str, "mixscan")) { + /* Get mix scan number */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &mix_scan_num); + if (ret) + return; + chip->u_drive_mode.s_drive_mode.led_mode =3D mix_scan_num + 4; + /* Get mix_sel_led */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &mix_sel_led); + if (ret) + return; + set_mix_sel_led(chip, mix_sel_led); + if (mix_scan_num =3D=3D 1) { + /* Get scan_order0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + } + if (mix_scan_num =3D=3D 2) { + /* Get scan_order0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + /* Get scan_order1 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder1); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + chip->u_scan_order.s_scan_order.scan_order_1 =3D + scan_oder1; + } + if (mix_scan_num =3D=3D 3) { + /* Get scan_order0 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder0); + if (ret) + return; + /* Get scan_order1 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder1); + if (ret) + return; + /* Get scan_order2 */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &scan_oder2); + if (ret) + return; + chip->u_scan_order.s_scan_order.scan_order_0 =3D + scan_oder0; + chip->u_scan_order.s_scan_order.scan_order_1 =3D + scan_oder1; + chip->u_scan_order.s_scan_order.scan_order_2 =3D + scan_oder2; + } + } +} + +static void leds_remove_existed_sysfs(struct lp5812_chip *chip) +{ + int i; + + for (i =3D 0; i < MAX_LEDS; i++) { + if (chip->leds[i].is_sysfs_created) + lp5812_remove_sysfs_group(&chip->leds[i]); + } +} + +static int lp5812_init_dev_config(struct lp5812_chip *chip, + const char *drive_mode, int rm_led_sysfs) +{ + int i; + int num_drive_mode; + int is_valid_arg =3D 0; + char *str; + const char **led_ptr; + + str =3D kstrdup(drive_mode, GFP_KERNEL); + if (!str) + return -ENOMEM; + + num_drive_mode =3D ARRAY_SIZE(chip_leds_map); + for (i =3D 0; i < num_drive_mode; ++i) { + if (sysfs_streq(str, chip_leds_map[i].drive_mode)) { + if (rm_led_sysfs) + leds_remove_existed_sysfs(chip); + parse_drive_mode(chip, str); + led_ptr =3D chip_leds_map[i].led_arr; + while (*led_ptr) { + lp5812_create_sysfs_via_led_name(chip, + *led_ptr); + ++led_ptr; + } + is_valid_arg =3D 1; + break; + } + } + kfree(str); + if (!is_valid_arg) + return -EINVAL; + + return 0; +} + +static ssize_t dev_config_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + int ret; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + mutex_lock(&chip->lock); + ret =3D lp5812_init_dev_config(chip, buf, 1); + if (ret) { + dev_err(chip->dev, "%s: lp5812_init_dev_config failed\n", + __func__); + goto out; + } + + /* set drive mode and scan order to hardware */ + ret =3D lp5812_set_drive_mode_scan_order(chip); + if (ret) { + ret =3D -EIO; + goto out; + } + ret =3D lp5812_update_regs_config(chip); + if (ret) { + ret =3D -EIO; + goto out; + } + + /* disable all LEDs that were previously enabled */ + ret =3D lp5812_disable_all_leds(chip); + if (ret) { + ret =3D -EIO; + goto out; + } + mutex_unlock(&chip->lock); + + return len; +out: + mutex_unlock(&chip->lock); + return ret; +} + +static char *parse_dev_config_info(struct lp5812_chip *chip) +{ + char *tmp; + int order_0, order_1, order_2, order_3; + char *scan_order_0 =3D " scan_order_0: "; + char *scan_order_1 =3D " scan_order_1: "; + char *scan_order_2 =3D " scan_order_2: "; + char *scan_order_3 =3D " scan_order_3: "; + + tmp =3D kmalloc(128, GFP_KERNEL); + if (!tmp) + return NULL; + + order_0 =3D chip->u_scan_order.s_scan_order.scan_order_0; + order_1 =3D chip->u_scan_order.s_scan_order.scan_order_1; + order_2 =3D chip->u_scan_order.s_scan_order.scan_order_2; + order_3 =3D chip->u_scan_order.s_scan_order.scan_order_3; + + switch (chip->u_drive_mode.s_drive_mode.led_mode) { + case DIRECT_MODE: + sprintf(tmp, "%s", "Mode: direct mode;"); + break; + case TCM_1_SCAN: + sprintf(tmp, "%s%s%d%s", "Mode: tcm 1 scan;", scan_order_0, + order_0, ";"); + break; + case TCM_2_SCAN: + sprintf(tmp, "%s%s%d%s%s%d%s", "Mode: tcm 2 scan;", + scan_order_0, order_0, ";", scan_order_1, order_1, ";"); + break; + case TCM_3_SCAN: + sprintf(tmp, "%s%s%d%s%s%d%s%s%d%s", "Mode: tcm 3 scan;", + scan_order_0, order_0, ";", scan_order_1, order_1, + ";", scan_order_2, order_2, ";"); + break; + case TCM_4_SCAN: + sprintf(tmp, "%s%s%d%s%s%d%s%s%d%s%s%d%s", "Mode: tcm 4 scan;", + scan_order_0, order_0, ";", scan_order_1, order_1, ";", + scan_order_2, order_2, ";", scan_order_3, order_3, ";"); + break; + case MIX_1_SCAN: + sprintf(tmp, "%s%s%d%s", "Mode: mix 1 scan;", scan_order_0, + order_0, ";"); + break; + case MIX_2_SCAN: + sprintf(tmp, "%s%s%d%s%s%d%s", "Mode: mix 2 scan;", + scan_order_0, order_0, ";", scan_order_1, order_1, ";"); + break; + case MIX_3_SCAN: + sprintf(tmp, "%s%s%d%s%s%d%s%s%d%s", "Mode: mix 3 scan;", + scan_order_0, order_0, ";", scan_order_1, order_1, + ";", scan_order_2, order_2, ";"); + break; + } + + if (chip->u_drive_mode.s_drive_mode.mix_sel_led_0) + strcat(tmp, " Direct output: OUT0;"); + else if (chip->u_drive_mode.s_drive_mode.mix_sel_led_1) + strcat(tmp, " Direct output: OUT1;"); + else if (chip->u_drive_mode.s_drive_mode.mix_sel_led_2) + strcat(tmp, " Direct output: OUT2;"); + else if (chip->u_drive_mode.s_drive_mode.mix_sel_led_3) + strcat(tmp, " Direct output: OUT3;"); + else + strcat(tmp, " Direct output: None;"); + return tmp; +} + +static ssize_t dev_config_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int i; + char *mode_info; + int ret; + int pos =3D 0; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + /* get drive mode and scan order */ + mutex_lock(&chip->lock); + ret =3D lp5812_get_drive_mode_scan_order(chip); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + mode_info =3D parse_dev_config_info(chip); + if (!mode_info) + return -ENOMEM; + + pos +=3D sysfs_emit_at(buf, pos, "%s", mode_info); + pos +=3D sysfs_emit_at(buf, pos, "%s", "\nPlease select below valid drive= mode:\n"); + pos +=3D sysfs_emit_at(buf, pos, "%s", "For Ex: echo tcmscan:1:0 > dev_co= nfig\n"); + for (i =3D 0; i < ARRAY_SIZE(chip_leds_map); ++i) + pos +=3D sysfs_emit_at(buf, pos, "%s\n", chip_leds_map[i].drive_mode); + kfree(mode_info); + + return pos; +} + +static ssize_t tsd_config_status_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_val; + int tsd_stat; + int config_stat; + struct lp5812_chip *chip =3D i2c_get_clientdata(to_i2c_client(dev)); + + mutex_lock(&chip->lock); + ret =3D lp5812_read(chip, (u16)TSD_CONFIG_STAT_REG, ®_val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + tsd_stat =3D (reg_val >> 1) & 0x01; + config_stat =3D reg_val & 0x01; + + return sysfs_emit(buf, "%d %d\n", tsd_stat, config_stat); +} + +static LP5812_DEV_ATTR_RW(device_enable); +static LP5812_DEV_ATTR_RW(dev_config); +static LP5812_DEV_ATTR_WO(device_command); +static LP5812_DEV_ATTR_WO(sw_reset); +static LP5812_DEV_ATTR_WO(fault_clear); +static LP5812_DEV_ATTR_RO(tsd_config_status); + +static struct attribute *lp5812_chip_attributes[] =3D { + &dev_attr_device_enable.attr, + &dev_attr_device_command.attr, + &dev_attr_sw_reset.attr, + &dev_attr_fault_clear.attr, + &dev_attr_dev_config.attr, + &dev_attr_tsd_config_status.attr, + NULL +}; + +static ssize_t led_activate_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u8 val; + u16 reg; + u8 reg_val; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + if (led->led_number < 0x8) + reg =3D LED_ENABLE_1_REG; + else + reg =3D LED_ENABLE_2_REG; + + mutex_lock(&chip->lock); + ret =3D lp5812_read(chip, reg, ®_val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + val =3D (reg_val & (1 << (led->led_number % 8))) ? 1 : 0; + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t led_activate_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int val; + u16 reg; + u8 reg_val; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + ret =3D kstrtoint(buf, 0, &val); + if (ret) + return -EINVAL; + + if (val !=3D 0 && val !=3D 1) + return -EINVAL; + + if (led->led_number < 0x8) + reg =3D LED_ENABLE_1_REG; + else + reg =3D LED_ENABLE_2_REG; + + mutex_lock(&chip->lock); + ret =3D lp5812_read(chip, reg, ®_val); + if (ret =3D=3D 0) { + if (val =3D=3D 0) { + ret =3D lp5812_write(chip, reg, + reg_val & (~(1 << (led->led_number % 8)))); + } else { + ret =3D lp5812_write(chip, reg, + reg_val | (1 << (led->led_number % 8))); + } + } + mutex_unlock(&chip->lock); + + if (ret) + return -EIO; + + return count; +} + +static ssize_t led_mode_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + enum control_mode mode; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_get_led_mode(chip, led->led_number, &mode); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%s\n", AUTONOMOUS =3D=3D mode ? "autonomous" : "m= anual"); +} + +static ssize_t led_mode_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret; + enum control_mode val; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + if (sysfs_streq(buf, "manual")) { + /* Remove AEU sysfs interface for current led in manual mode */ + aeu_remove_multi_sysfs_groups(led); + val =3D MANUAL; + } else if (sysfs_streq(buf, "autonomous")) { + val =3D AUTONOMOUS; + } else { + return -EINVAL; + } + + mutex_lock(&chip->lock); + ret =3D lp5812_set_led_mode(chip, led->led_number, val); + if (ret) { + ret =3D -EIO; + goto out; + } + + ret =3D aeu_create_multi_sysfs_groups(led); + if (ret) { + dev_err(chip->dev, "aeu_create_multi_sysfs_groups() failed\n"); + goto out; + } + + mutex_unlock(&chip->lock); + return count; +out: + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t led_manual_dc_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int manual_dc; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + ret =3D kstrtoint(buf, 0, &manual_dc); + if (ret) + return ret; + + if (manual_dc < 0 || manual_dc > 255) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D lp5812_manual_dc_pwm_control(chip, led->led_number, manual_dc, AN= ALOG); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t led_manual_dc_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u8 val; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_manual_dc_pwm_read(chip, led->led_number, &val, ANALOG); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t led_manual_pwm_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int manual_pwm; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + ret =3D kstrtoint(buf, 0, &manual_pwm); + if (ret) + return ret; + + if (manual_pwm < 0 || manual_pwm > 255) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D lp5812_manual_dc_pwm_control(chip, led->led_number, manual_pwm, P= WM); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t led_manual_pwm_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u8 val; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_manual_dc_pwm_read(chip, led->led_number, &val, PWM); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t led_auto_dc_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int auto_dc; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + ret =3D kstrtoint(buf, 0, &auto_dc); + if (ret) + return ret; + + if (auto_dc < 0 || auto_dc > 255) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D lp5812_autonomous_dc_pwm_control(chip, led->led_number, auto_dc, = ANALOG); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t led_auto_dc_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + u8 val; + int ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_autonomous_dc_pwm_read(chip, led->led_number, &val, ANALOG= ); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", val); +} + +static int parse_autonomous_animation_config(struct lp5812_led *led, + const char *user_buf) +{ + int ret; + int i; + char *str; + char *sub_str; + int aeu_select, start_pause_time, stop_pause_time, led_playback_time; + + str =3D kstrdup(user_buf, GFP_KERNEL); + if (!str) + return -ENOMEM; + + /* parse aeu_select */ + sub_str =3D strsep(&str, ":"); + ret =3D kstrtoint(sub_str, 0, &aeu_select); + if (ret) + goto free_str; + if (aeu_select < 1 || aeu_select > 3) { + ret =3D -EINVAL; + goto free_str; + } + + aeu_remove_multi_sysfs_groups(led); + + for (i =3D 0; i < aeu_select; i++) + aeu_create_sysfs_group(&led->aeu[i]); + led->led_playback.s_led_playback.aeu_selection =3D aeu_select - 1; + + /* parse start_pause_time */ + sub_str =3D strsep(&str, ":"); + if (sub_str) { + ret =3D kstrtoint(sub_str, 0, &start_pause_time); + if (ret) + goto free_str; + if (start_pause_time < 0 || start_pause_time > 15) { + ret =3D -EINVAL; + goto free_str; + } + led->start_stop_pause_time.s_time.second =3D start_pause_time; + } else { + led->start_stop_pause_time.s_time.second =3D 15; + } + + /* parse stop_pause_time */ + sub_str =3D strsep(&str, ":"); + if (sub_str) { + ret =3D kstrtoint(sub_str, 0, &stop_pause_time); + if (ret) + goto free_str; + if (stop_pause_time < 0 || stop_pause_time > 15) { + ret =3D -EINVAL; + goto free_str; + } + led->start_stop_pause_time.s_time.first =3D stop_pause_time; + } else { + led->start_stop_pause_time.s_time.first =3D 15; + } + + /* parse led_playback_time */ + sub_str =3D strsep(&str, ":"); + if (sub_str) { + ret =3D kstrtoint(sub_str, 0, &led_playback_time); + if (ret) + goto free_str; + if (led_playback_time < 0 || led_playback_time > 15) { + ret =3D -EINVAL; + goto free_str; + } + led->led_playback.s_led_playback.led_playback_time =3D + led_playback_time; + } else { + led->led_playback.s_led_playback.led_playback_time =3D 15; + } + + kfree(str); + return 0; + +free_str: + kfree(str); + return ret; +} + +static ssize_t led_auto_animation_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret; + enum control_mode led_mode; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + /* First check the mode of led is manual or autonomous */ + ret =3D lp5812_get_led_mode(chip, led->led_number, &led_mode); + if (ret) { + ret =3D -EIO; + goto out; + } + + if (led_mode =3D=3D AUTONOMOUS) { + ret =3D parse_autonomous_animation_config(led, buf); + if (ret) { + dev_err(chip->dev, "parse_autonomous_animation_config failed\n"); + goto out; + } + /* Write data to hardware */ + ret =3D led_set_autonomous_animation_config(led); + if (ret) { + ret =3D -EIO; + goto out; + } + } + mutex_unlock(&chip->lock); + return count; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t led_auto_animation_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int aeu_selection, playback_time, start_pause, stop_pause; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + int pos =3D 0; + int ret; + + mutex_lock(&chip->lock); + ret =3D led_get_autonomous_animation_config(led); + if (ret) { + ret =3D -EIO; + goto out; + } + + /* parse config and feedback to userspace */ + aeu_selection =3D led->led_playback.s_led_playback.aeu_selection; + playback_time =3D led->led_playback.s_led_playback.led_playback_time; + start_pause =3D led->start_stop_pause_time.s_time.second; + stop_pause =3D led->start_stop_pause_time.s_time.first; + mutex_unlock(&chip->lock); + + pos +=3D sysfs_emit_at(buf, pos, "AEU Select: "); + if (aeu_selection =3D=3D ONLY_AEU1) + pos +=3D sysfs_emit_at(buf, pos, "Only use AEU1"); + else if (aeu_selection =3D=3D AEU1_AEU2) + pos +=3D sysfs_emit_at(buf, pos, "Use AEU1 and AEU2"); + else + pos +=3D sysfs_emit_at(buf, pos, "Use AEU1, AEU2 and AEU3"); + + pos +=3D sysfs_emit_at(buf, pos, "; Start pause time: %s", + time_name_array[start_pause]); + pos +=3D sysfs_emit_at(buf, pos, "; Stop pause time: %s", + time_name_array[stop_pause]); + pos +=3D sysfs_emit_at(buf, pos, "; LED Playback time: %s", + led_playback_time_arr[playback_time]); + + pos +=3D sysfs_emit_at(buf, pos, "\n"); + pos +=3D sysfs_emit_at(buf, pos, "Command usage: echo (aeu number):(start= pause time):"); + pos +=3D sysfs_emit_at(buf, pos, "(stop pause time):(playback time) > aut= o_animation\n"); + + return pos; + +out: + mutex_unlock(&chip->lock); + return ret; +} + +static ssize_t led_lod_lsd_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + u8 lsd_status, lod_status; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_read_lsd_status(chip, led->led_number, &lsd_status); + if (!ret) + ret =3D lp5812_read_lod_status(chip, led->led_number, &lod_status); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d %d\n", lod_status, lsd_status); +} + +static ssize_t led_auto_pwm_val_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + u8 auto_pwm_val; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_read_auto_pwm_value(chip, led->led_number, &auto_pwm_val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", auto_pwm_val); +} + +static ssize_t led_aep_status_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + u8 aep_status; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_read_aep_status(chip, led->led_number, &aep_status); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", aep_status); +} + +static ssize_t led_pwm_phase_align_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int val, ret; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + if (sysfs_streq(buf, "forward")) + val =3D 0; + else if (sysfs_streq(buf, "middle")) + val =3D 2; + else if (sysfs_streq(buf, "backward")) + val =3D 3; + else + return -EINVAL; + mutex_lock(&chip->lock); + ret =3D lp5812_set_phase_align(chip, led->led_number, val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t led_pwm_phase_align_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int val, ret; + char *str; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_get_phase_align(chip, led->led_number, &val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + if (val =3D=3D 0 || val =3D=3D 1) + str =3D "forward"; + else if (val =3D=3D 2) + str =3D "middle"; + else + str =3D "backward"; + + return sysfs_emit(buf, "%s\n", str); +} + +static ssize_t led_pwm_dimming_scale_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int ret; + enum pwm_dimming_scale val; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + if (sysfs_streq(buf, "linear")) + val =3D LINEAR; + else if (sysfs_streq(buf, "exponential")) + val =3D EXPONENTIAL; + else + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D lp5812_set_pwm_dimming_scale(chip, led->led_number, val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t led_pwm_dimming_scale_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + enum pwm_dimming_scale scale; + struct lp5812_led *led =3D to_lp5812_led(kobj); + struct lp5812_chip *chip =3D led->priv; + + mutex_lock(&chip->lock); + ret =3D lp5812_get_pwm_dimming_scale(chip, led->led_number, &scale); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%s\n", + scale =3D=3D EXPONENTIAL ? "exponential" : "linear"); +} + +static LP5812_KOBJ_ATTR_RW(activate, led_activate_show, led_activate_store= ); +static LP5812_KOBJ_ATTR_RW(mode, led_mode_show, led_mode_store); +static LP5812_KOBJ_ATTR_RW(manual_dc, led_manual_dc_show, + led_manual_dc_store); +static LP5812_KOBJ_ATTR_RW(manual_pwm, led_manual_pwm_show, + led_manual_pwm_store); +static LP5812_KOBJ_ATTR_RW(autonomous_dc, led_auto_dc_show, + led_auto_dc_store); +static LP5812_KOBJ_ATTR_RW(autonomous_animation, led_auto_animation_show, + led_auto_animation_store); +static LP5812_KOBJ_ATTR_RW(pwm_phase_align, led_pwm_phase_align_show, + led_pwm_phase_align_store); +static LP5812_KOBJ_ATTR_RW(pwm_dimming_scale, led_pwm_dimming_scale_show, + led_pwm_dimming_scale_store); +static LP5812_KOBJ_ATTR_RO(lod_lsd, led_lod_lsd_show); +static LP5812_KOBJ_ATTR_RO(auto_pwm_val, led_auto_pwm_val_show); +static LP5812_KOBJ_ATTR_RO(aep_status, led_aep_status_show); + +static struct attribute *led_kobj_attributes[] =3D { + &kobj_attr_activate.attr, + &kobj_attr_mode.attr, + &kobj_attr_manual_dc.attr, + &kobj_attr_manual_pwm.attr, + &kobj_attr_autonomous_dc.attr, + &kobj_attr_autonomous_animation.attr, + &kobj_attr_lod_lsd.attr, + &kobj_attr_auto_pwm_val.attr, + &kobj_attr_aep_status.attr, + &kobj_attr_pwm_phase_align.attr, + &kobj_attr_pwm_dimming_scale.attr, + NULL +}; + +static ssize_t aeu_pwm_store(struct kobject *kobj, struct kobj_attribute *= attr, + const char *buf, size_t count, enum pwm_slope_time_num pwm_channel) +{ + int val; + int ret; + struct anim_engine_unit *aeu =3D to_anim_engine_unit(kobj); + struct lp5812_chip *chip =3D aeu->led->priv; + + ret =3D kstrtoint(buf, 0, &val); + if (ret) + return -EINVAL; + if (val < 0 || val > 255) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D led_aeu_pwm_set_val(aeu, val, pwm_channel); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t aeu_pwm_show(struct kobject *kobj, struct kobj_attribute *a= ttr, + char *buf, enum pwm_slope_time_num pwm_channel) +{ + int ret; + u8 val; + struct anim_engine_unit *aeu =3D to_anim_engine_unit(kobj); + struct lp5812_chip *chip =3D aeu->led->priv; + + mutex_lock(&chip->lock); + ret =3D led_aeu_pwm_get_val(aeu, &val, pwm_channel); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t aeu_pwm1_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_pwm_store(kobj, attr, buf, count, PWM1); +} + +static ssize_t aeu_pwm1_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_pwm_show(kobj, attr, buf, PWM1); +} + +static ssize_t aeu_pwm2_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_pwm_store(kobj, attr, buf, count, PWM2); +} + +static ssize_t aeu_pwm2_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_pwm_show(kobj, attr, buf, PWM2); +} + +static ssize_t aeu_pwm3_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_pwm_store(kobj, attr, buf, count, PWM3); +} + +static ssize_t aeu_pwm3_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_pwm_show(kobj, attr, buf, PWM3); +} + +static ssize_t aeu_pwm4_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_pwm_store(kobj, attr, buf, count, PWM4); +} + +static ssize_t aeu_pwm4_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_pwm_show(kobj, attr, buf, PWM4); +} + +static ssize_t aeu_pwm5_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_pwm_store(kobj, attr, buf, count, PWM5); +} + +static ssize_t aeu_pwm5_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_pwm_show(kobj, attr, buf, PWM5); +} + +static ssize_t aeu_slope_time_store(struct kobject *kobj, struct kobj_attr= ibute *attr, + const char *buf, size_t count, enum pwm_slope_time_num slope_time_num) +{ + int val; + int ret; + struct anim_engine_unit *aeu =3D to_anim_engine_unit(kobj); + struct lp5812_chip *chip =3D aeu->led->priv; + + ret =3D kstrtoint(buf, 0, &val); + if (ret) + return -EINVAL; + + if (val < 0x00 || val > 0x0F) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D led_aeu_slope_time_set_val(aeu, val, slope_time_num); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t aeu_slope_time_show(struct kobject *kobj, struct kobj_attri= bute *attr, + char *buf, enum pwm_slope_time_num slope_time_num) +{ + int ret; + u8 val; + struct anim_engine_unit *aeu =3D to_anim_engine_unit(kobj); + struct lp5812_chip *chip =3D aeu->led->priv; + + mutex_lock(&chip->lock); + ret =3D led_aeu_slope_time_get_val(aeu, &val, slope_time_num); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", val); +} + +static ssize_t aeu_slope_time_t1_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T1); +} + +static ssize_t aeu_slope_time_t1_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_slope_time_show(kobj, attr, buf, SLOPE_T1); +} + +static ssize_t aeu_slope_time_t2_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T2); +} + +static ssize_t aeu_slope_time_t2_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_slope_time_show(kobj, attr, buf, SLOPE_T2); +} + +static ssize_t aeu_slope_time_t3_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T3); +} + +static ssize_t aeu_slope_time_t3_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_slope_time_show(kobj, attr, buf, SLOPE_T3); +} + +static ssize_t aeu_slope_time_t4_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + return aeu_slope_time_store(kobj, attr, buf, count, SLOPE_T4); +} + +static ssize_t aeu_slope_time_t4_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + return aeu_slope_time_show(kobj, attr, buf, SLOPE_T4); +} + +static ssize_t aeu_playback_time_store(struct kobject *kobj, + struct kobj_attribute *attr, const char *buf, size_t count) +{ + int val; + int ret; + struct anim_engine_unit *aeu =3D to_anim_engine_unit(kobj); + struct lp5812_chip *chip =3D aeu->led->priv; + + ret =3D kstrtoint(buf, 0, &val); + if (ret) + return -EINVAL; + + if (val < 0 || val > 3) + return -EINVAL; + + mutex_lock(&chip->lock); + ret =3D led_aeu_playback_time_set_val(aeu, val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return count; +} + +static ssize_t aeu_playback_time_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + int ret; + u8 val; + struct anim_engine_unit *aeu =3D to_anim_engine_unit(kobj); + struct lp5812_chip *chip =3D aeu->led->priv; + + mutex_lock(&chip->lock); + ret =3D led_aeu_playback_time_get_val(aeu, &val); + mutex_unlock(&chip->lock); + if (ret) + return -EIO; + + return sysfs_emit(buf, "%d\n", val); +} + +static LP5812_KOBJ_ATTR_RW(pwm1, aeu_pwm1_show, aeu_pwm1_store); +static LP5812_KOBJ_ATTR_RW(pwm2, aeu_pwm2_show, aeu_pwm2_store); +static LP5812_KOBJ_ATTR_RW(pwm3, aeu_pwm3_show, aeu_pwm3_store); +static LP5812_KOBJ_ATTR_RW(pwm4, aeu_pwm4_show, aeu_pwm4_store); +static LP5812_KOBJ_ATTR_RW(pwm5, aeu_pwm5_show, aeu_pwm5_store); +static LP5812_KOBJ_ATTR_RW(slope_time_t1, aeu_slope_time_t1_show, + aeu_slope_time_t1_store); +static LP5812_KOBJ_ATTR_RW(slope_time_t2, aeu_slope_time_t2_show, + aeu_slope_time_t2_store); +static LP5812_KOBJ_ATTR_RW(slope_time_t3, aeu_slope_time_t3_show, + aeu_slope_time_t3_store); +static LP5812_KOBJ_ATTR_RW(slope_time_t4, aeu_slope_time_t4_show, + aeu_slope_time_t4_store); +static LP5812_KOBJ_ATTR_RW(playback_time, aeu_playback_time_show, + aeu_playback_time_store); + +static struct attribute *aeu_kobj_attributes[] =3D { + &kobj_attr_pwm1.attr, + &kobj_attr_pwm2.attr, + &kobj_attr_pwm3.attr, + &kobj_attr_pwm4.attr, + &kobj_attr_pwm5.attr, + &kobj_attr_slope_time_t1.attr, + &kobj_attr_slope_time_t2.attr, + &kobj_attr_slope_time_t3.attr, + &kobj_attr_slope_time_t4.attr, + &kobj_attr_playback_time.attr, + NULL +}; + +static void aeu_init_properties(struct lp5812_led *led) +{ + int i; + + for (i =3D 0; i < MAX_AEU; i++) { + led->aeu[i].aeu_name =3D aeu_name_array[i]; + led->aeu[i].aeu_number =3D i + 1; + led->aeu[i].led =3D led; + led->aeu[i].enabled =3D 0; + led->aeu[i].attr_group.attrs =3D aeu_kobj_attributes; + kobject_init(&led->aeu[i].kobj, &aeu_ktype); + } +} + +static int lp5812_probe(struct i2c_client *client) +{ + struct lp5812_chip *chip; + int i; + int ret; + u8 val; + + chip =3D devm_kzalloc(&client->dev, sizeof(struct lp5812_chip), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + mutex_init(&chip->lock); + chip->i2c_cl =3D client; + chip->dev =3D &client->dev; + chip->regs =3D ®s; + chip->command =3D NONE; + chip->attr_group.name =3D "lp5812_chip_setup"; + chip->attr_group.attrs =3D lp5812_chip_attributes; + chip->chip_leds_map =3D chip_leds_map; + chip->u_drive_mode.drive_mode_val =3D 0x10; + chip->u_scan_order.scan_order_val =3D 0x00; + + /* initialize property for each led */ + for (i =3D 0; i < MAX_LEDS; i++) { + chip->leds[i].led_name =3D led_name_array[i]; + chip->leds[i].led_number =3D i; + chip->leds[i].anim_base_addr =3D anim_base_addr_array[i]; + chip->leds[i].enable =3D 0; /* LED disable as default */ + chip->leds[i].mode =3D MANUAL; /* manual mode as default */ + chip->leds[i].priv =3D chip; + chip->leds[i].led_playback.led_playback_val =3D 0; + chip->leds[i].start_stop_pause_time.time_val =3D 0; + /* sysfs for this led not be created */ + chip->leds[i].is_sysfs_created =3D 0; + chip->leds[i].attr_group.attrs =3D led_kobj_attributes; + kobject_init(&chip->leds[i].kobj, &led_ktype); + + /* init animation engine unit properties */ + aeu_init_properties(&chip->leds[i]); + + /* set autonomous animation config as default for all LEDs */ + led_set_autonomous_animation_config(&chip->leds[i]); + } + + i2c_set_clientdata(client, chip); + + ret =3D sysfs_create_group(&chip->dev->kobj, &chip->attr_group); + if (ret) + return dev_err_probe(chip->dev, ret, "sysfs_create_group failed\n"); + + ret =3D lp5812_init_dev_config(chip, "tcmscan:4:0:1:2:3", 0); + if (ret) + return dev_err_probe(chip->dev, ret, "%s: lp5812_init_dev_config failed\= n", + __func__); + + ret =3D lp5812_initialize(chip); + if (ret) + return dev_err_probe(chip->dev, ret, "lp5812 initialize failed\n"); + + /* code to verify i2c read/write ok or not */ + lp5812_read(chip, (u16)DEV_CONFIG2, &val); + + lp5812_write(chip, (u16)LED_A1_AUTO_BASE_ADRR, 0x14); + lp5812_read(chip, (u16)LED_A1_AUTO_BASE_ADRR, &val); + /* End code to verify i2c read/write*/ + + return 0; +} + +static void lp5812_remove(struct i2c_client *client) +{ + struct lp5812_chip *chip =3D i2c_get_clientdata(client); + + mutex_destroy(&chip->lock); + leds_remove_existed_sysfs(chip); + sysfs_remove_group(&chip->dev->kobj, &chip->attr_group); + + lp5812_disable_all_leds(chip); + + lp5812_enable_disable(chip, 0); +} + +static const struct i2c_device_id lp5812_id[] =3D { + { "lp5812" }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, lp5812_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_lp5812_match[] =3D { + { .compatible =3D "ti,lp5812", }, + {/* NULL */} +}; + +MODULE_DEVICE_TABLE(of, of_lp5812_match); +#endif + +static struct i2c_driver lp5812_driver =3D { + .driver =3D { + .name =3D "lp5812", + .of_match_table =3D of_match_ptr(of_lp5812_match), + }, + .probe =3D lp5812_probe, + .remove =3D lp5812_remove, + .id_table =3D lp5812_id +}; + +module_i2c_driver(lp5812_driver); + +MODULE_DESCRIPTION("Texas Instruments LP5812 LED Driver"); +MODULE_AUTHOR("Jared Zhou"); +MODULE_LICENSE("GPL"); diff --git a/drivers/auxdisplay/lp5812.h b/drivers/auxdisplay/lp5812.h new file mode 100644 index 000000000000..be48b9474116 --- /dev/null +++ b/drivers/auxdisplay/lp5812.h @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * LP5812 Driver Header + * + * Copyright (C) 2025 Texas Instruments + * + * Author: Jared Zhou + */ + +#ifndef _LP5812_H_ +#define _LP5812_H_ + +#include +#include +#include +#include +#include +#include +#include + +#define CHIP_EN_REG 0x00 + +#define DEV_CONFIG0 0x01 +#define DEV_CONFIG1 0x02 +#define DEV_CONFIG2 0x03 +#define DEV_CONFIG3 0x04 +#define DEV_CONFIG4 0x05 +#define DEV_CONFIG5 0x06 +#define DEV_CONFIG6 0x07 +#define DEV_CONFIG7 0x08 +#define DEV_CONFIG8 0x09 +#define DEV_CONFIG9 0x0A +#define DEV_CONFIG10 0x0B +#define DEV_CONFIG11 0x0c +#define DEV_CONFIG12 0x0D + +#define CMD_UPDATE_REG 0x10 +#define CMD_START_REG 0x11 +#define CMD_STOP_REG 0x12 +#define CMD_PAUSE_REG 0x13 +#define CMD_CONTINUE_REG 0x14 + +#define LED_ENABLE_1_REG 0x20 +#define LED_ENABLE_2_REG 0x21 + +#define FAULT_CLEAR_REG 0x22 +#define RESET_REG 0x23 + +#define MANUAL_DC_LED_0_REG 0x30 +#define MANUAL_PWM_LED_0_REG 0x40 +#define AUTO_DC_LED_0_REG 0x50 + +/* value for register */ +#define UPDATE_CMD_VAL 0x55 +#define START_CMD_VAL 0xFF +#define STOP_CMD_VAL 0xAA +#define PAUSE_CMD_VAL 0x33 +#define CONTINUE_CMD_VAL 0xCC + +#define CHIP_ENABLE 0x01 +#define CHIP_DISABLE 0x00 + +#define FAULT_CLEAR_ALL 0x07 +#define TSD_CLEAR_VAL 0x04 +#define LSD_CLEAR_VAL 0x02 +#define LOD_CLEAR_VAL 0x01 +#define RESET_REG_VAL 0x66 + +#define LED0_AUTO_BASE_ADRR 0x80 +#define LED1_AUTO_BASE_ADRR 0x9A +#define LED2_AUTO_BASE_ADRR 0xB4 +#define LED3_AUTO_BASE_ADRR 0xCE +#define LED_A0_AUTO_BASE_ADRR 0xE8 +#define LED_A1_AUTO_BASE_ADRR 0x102 +#define LED_A2_AUTO_BASE_ADRR 0x11C +#define LED_B0_AUTO_BASE_ADRR 0x136 +#define LED_B1_AUTO_BASE_ADRR 0x150 +#define LED_B2_AUTO_BASE_ADRR 0x16A +#define LED_C0_AUTO_BASE_ADRR 0x184 +#define LED_C1_AUTO_BASE_ADRR 0x19E +#define LED_C2_AUTO_BASE_ADRR 0x1B8 +#define LED_D0_AUTO_BASE_ADRR 0x1D2 +#define LED_D1_AUTO_BASE_ADRR 0x1EC +#define LED_D2_AUTO_BASE_ADRR 0x206 + +/* Flag Registers */ +#define TSD_CONFIG_STAT_REG 0x300 +#define LOD_STAT_1_REG 0x301 +#define LOD_STAT_2_REG 0x302 +#define LSD_STAT_1_REG 0x303 +#define LSD_STAT_2_REG 0x304 + +#define AUTO_PWM_BASE_ADDR 0x305 + +#define AEP_STATUS_0_REG 0x315 +#define AEP_STATUS_1_REG 0x316 +#define AEP_STATUS_2_REG 0x317 +#define AEP_STATUS_3_REG 0x318 +#define AEP_STATUS_4_REG 0x319 +#define AEP_STATUS_5_REG 0x31A +#define AEP_STATUS_6_REG 0x31B +#define AEP_STATUS_7_REG 0x31C + +#define LED0 "led_0" +#define LED1 "led_1" +#define LED2 "led_2" +#define LED3 "led_3" +#define LED_A0 "led_A0" +#define LED_A1 "led_A1" +#define LED_A2 "led_A2" +#define LED_B0 "led_B0" +#define LED_B1 "led_B1" +#define LED_B2 "led_B2" +#define LED_C0 "led_C0" +#define LED_C1 "led_C1" +#define LED_C2 "led_C2" +#define LED_D0 "led_D0" +#define LED_D1 "led_D1" +#define LED_D2 "led_D2" + +/* Below define time for (start/stop/slope time) */ +#define TIME0 "no time" +#define TIME1 "0.09s" +#define TIME2 "0.18s" +#define TIME3 "0.36s" +#define TIME4 "0.54s" +#define TIME5 "0.80s" +#define TIME6 "1.07s" +#define TIME7 "1.52s" +#define TIME8 "2.06s" +#define TIME9 "2.50s" +#define TIME10 "3.04s" +#define TIME11 "4.02s" +#define TIME12 "5.01s" +#define TIME13 "5.99s" +#define TIME14 "7.06s" +#define TIME15 "8.05s" +/* End define time for (start/stop/slope time) */ + +#define AEU1 "AEU1" +#define AEU2 "AEU2" +#define AEU3 "AEU3" + +#define MAX_LEDS 16 +#define MAX_TIME 16 +#define MAX_AEU 3 + +#define LP5812_DEV_ATTR_RW(name) \ + DEVICE_ATTR_RW(name) +#define LP5812_DEV_ATTR_RO(name) \ + DEVICE_ATTR_RO(name) +#define LP5812_DEV_ATTR_WO(name) \ + DEVICE_ATTR_WO(name) + +#define LP5812_KOBJ_ATTR(_name, _mode, _show, _store) \ + struct kobj_attribute kobj_attr_##_name =3D __ATTR(_name, _mode, _show, _= store) +#define LP5812_KOBJ_ATTR_RW(name, show, store) \ + LP5812_KOBJ_ATTR(name, 0644, show, store) +#define LP5812_KOBJ_ATTR_RO(name, show) \ + LP5812_KOBJ_ATTR(name, 0444, show, NULL) +#define LP5812_KOBJ_ATTR_WO(name, store) \ + LP5812_KOBJ_ATTR(name, 0200, NULL, store) + +enum pwm_slope_time_num { + PWM1 =3D 1, + PWM2, + PWM3, + PWM4, + PWM5, + SLOPE_T1, + SLOPE_T2, + SLOPE_T3, + SLOPE_T4 +}; + +enum dimming_type { + ANALOG, + PWM +}; + +enum pwm_dimming_scale { + LINEAR =3D 0, + EXPONENTIAL +}; + +enum control_mode { + MANUAL =3D 0, + AUTONOMOUS +}; + +enum device_command { + NONE, + UPDATE, + START, + STOP, + PAUSE, + CONTINUE +}; + +enum animation_addr { + AUTO_PAUSE =3D 0, + AUTO_PLAYBACK, + AEU1_PWM_1, + AEU1_PWM_2, + AEU1_PWM_3, + AEU1_PWM_4, + AEU1_PWM_5, + AEU1_T12, + AEU1_T34, + AEU1_PLAYBACK, + AEU2_PWM_1, + AEU2_PWM_2, + AEU2_PWM_3, + AEU2_PWM_4, + AEU2_PWM_5, + AEU2_T12, + AEU2_T34, + AEU2_PLAYBACK, + AEU3_PWM_1, + AEU3_PWM_2, + AEU3_PWM_3, + AEU3_PWM_4, + AEU3_PWM_5, + AEU3_T12, + AEU3_T34, + AEU3_PLAYBACK +}; + +enum drive_mode { + DIRECT_MODE =3D 0, + TCM_1_SCAN, + TCM_2_SCAN, + TCM_3_SCAN, + TCM_4_SCAN, + MIX_1_SCAN, + MIX_2_SCAN, + MIX_3_SCAN +}; + +enum aeu_select { + ONLY_AEU1, + AEU1_AEU2, + AEU1_AEU2_AEU3 +}; + +union time { + struct { + u8 first:4; + u8 second:4; + } __packed s_time; + u8 time_val; +}; /* type for start/stop pause time and slope time */ + +union led_playback { + struct { + u8 led_playback_time:4; + u8 aeu_selection:2; + u8 reserved:2; + } __packed s_led_playback; + u8 led_playback_val; +}; + +union scan_order { + struct { + u8 scan_order_0:2; + u8 scan_order_1:2; + u8 scan_order_2:2; + u8 scan_order_3:2; + } __packed s_scan_order; + u8 scan_order_val; +}; + +union drive_mode_info { + struct { + u8 mix_sel_led_0:1; + u8 mix_sel_led_1:1; + u8 mix_sel_led_2:1; + u8 mix_sel_led_3:1; + u8 led_mode:3; + u8 pwm_fre:1; + } __packed s_drive_mode; + u8 drive_mode_val; +}; + +struct drive_mode_led_map { + const char *drive_mode; + const char **led_arr; +}; + +struct lp5812_specific_regs { + u16 enable_reg; + u16 reset_reg; + u16 update_cmd_reg; + u16 start_cmd_reg; + u16 stop_cmd_reg; + u16 pause_cmd_reg; + u16 continue_cmd_reg; + u16 fault_clear_reg; + u16 tsd_config_status_reg; +}; + +struct anim_engine_unit { + struct kobject kobj; + struct lp5812_led *led; + struct attribute_group attr_group; + const char *aeu_name; + int aeu_number; /* start from 1 */ + + /* To know led using this AEU or not*/ + int enabled; +}; + +struct lp5812_led { + struct kobject kobj; + struct lp5812_chip *priv; + struct attribute_group attr_group; + int enable; + enum control_mode mode; + enum dimming_type dimming_type; + u8 lod_lsd; + u8 auto_pwm; + u8 aep_status; + u16 anim_base_addr; + int led_number; /* start from 0 */ + int is_sysfs_created; + const char *led_name; + + union led_playback led_playback; + union time start_stop_pause_time; + + struct anim_engine_unit aeu[MAX_AEU]; +}; + +struct lp5812_chip { + struct i2c_client *i2c_cl; + struct mutex lock; /* Protects access to device = registers */ + struct device *dev; + struct attribute_group attr_group; + const struct lp5812_specific_regs *regs; + const struct drive_mode_led_map *chip_leds_map; + enum device_command command; + union scan_order u_scan_order; + union drive_mode_info u_drive_mode; + + struct lp5812_led leds[MAX_LEDS]; +}; + +#endif /*_LP5812_H_*/ --=20 2.25.1 From nobody Fri Dec 19 15:50:24 2025 Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 48496248889; Sun, 27 Apr 2025 08:26:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742382; cv=none; b=g+hVtvKhqChQ7nUBimJA29VU3IGkLI0TI01HT6/QtUa9LiHs37qqoHxSG1uVeqM/4JCAuO2sZinDoULB5iL1BycnI9l1GcNMjhFJYaHNeW630+rCK0iU6j/siG8LYAIwHb7rh9Bc6mEDE8dgd9P5BCvBcodsnMIpYCQJspPiYIM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742382; c=relaxed/simple; bh=cXHKMMt9x+B+XoZXvvBTPnl/DdUA4LwPQQZ2ZK89Jv8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=dr2dE6I0tgwACQY+bNev3lJuPzmynDXzco0VKtXJP/ENJ87ICk3AItDF4b0CR5hepTPUczqjC12AvAH3hXlJ2XeEsjVAFj6A+nqh8qjWj+0llT8YoKHd1t1wN9s7NbRewIXXwqkcqFIftdIWv/fxahVTaZeMANeqLHw/K07aw+Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=KFap3sRr; arc=none smtp.client-ip=209.85.210.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="KFap3sRr" Received: by mail-pf1-f170.google.com with SMTP id d2e1a72fcca58-7394945d37eso3240630b3a.3; Sun, 27 Apr 2025 01:26:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745742377; x=1746347177; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=rAHOOZ2NfcJK36SwKCvU8hUdcb2kgW0MQQU/N5qjNJY=; b=KFap3sRrTUs6PZ/kR1pGcJh8Dn0hGowRI4rShN9JwenfOSVfnd4tqUNxwcStuIj9vj qyI9g5hMOdxHqPGK8YrqQDDX2GirlPnH0D99beSPDgKZ/n+Qao/G33rrwnBsRteZXQmj 6zkwO81kh4CpgwsEkS5jWTgXlztF03s7Q6LVbCE+VJ0UoOViO80mU1LLeReRTOYALPLJ 81TMm9qppCdQ8UMNEgF+/VJavl9d7sBeC4xCVE5gKI8L3sR8W3t2IkDLPdGYkFVBgMDO tahLbMhyD4wIetCXArP3tO4uainS+QTmxGHoQKagPw7XtvmNWT2GebcLfQLAqwmYbAi4 doiw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745742377; x=1746347177; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=rAHOOZ2NfcJK36SwKCvU8hUdcb2kgW0MQQU/N5qjNJY=; b=uhFgU0ssr35rt1GFk0jR2/U7bhlmhu3BxmQEPfrXuwaP6L2px6eRBXwtp2HVjWQABH Xi5R2/gWqyMAFxoJClzhHdga6UzBJybxNsZuA2BXRlGqq7vBT3li/elC6Z0njTulu/lJ Pz4POv6zHIvWOsf/z1EQDlSSFawQWm970egCGEDINjPn3SEh7aFEnbXeoqi6bwdKXeLw ffbeh8oyVROOTBo8sSNWZiFybGagTacTHX886Q3SdekGqeOXGUWk/LkG7L4w+Y0FGVJJ iV8dDXFH9uUUg5spt2EuEH2AweElVb+/5G08vGITWEN25wArLfMdL+fjlo+zZIGaxJod x6Jg== X-Forwarded-Encrypted: i=1; AJvYcCUOGKozKSFJT13ZkVbe/gUeJIUgsHEq2CoIcZZlLXk+ZzVvSKzy/nw0N13rxgmV/Tf6Cn7UUxnQ43tc@vger.kernel.org, AJvYcCVM0iETmz73k1GWRWR59AOcvFwKzsqw8EY7T+gTLdZFS6CWnmR+C+tll1tKsHYrZR2LIbPWxnuUinA+qYDr@vger.kernel.org, AJvYcCWpYtwy2qHrW7X/tP4Vk84GIAc5hDh9KG2t3nw43lS/Qqjsj3J6iD5m2R6H2L7kpKrsFO8nWGmp8iyc@vger.kernel.org X-Gm-Message-State: AOJu0YxK8+5+AEmKuyejy8zu+pIAeThzCFWTgPZsOEV6XvGlVQ3sYWSy +ywxY/oPK/iroXcYkzASLE2jFBdmmbrAqXWXTrYLUU79ivVHjV5u/XfWOF8R X-Gm-Gg: ASbGncv+ktkp82HEiRhbOHmtlPO8LtoUwzfjAiaG4m7hdbgvNLua+KHkwOCYt3Ym9ix 0hqBqdxGMddBT9jTApNbvGy03Heq0xh2bGK40AwLyANLgT6mzF9BNJ237nfWcCB1kgwnFgZ9nRa PkhflBTpCUhMHzikafhDAt/wpbH7ShfQecyDPc4m/hTCg/S3UiWhNE5/wXMb+TELImgDTSHoSwM 6bSkfXQs2M20f//xDati021FDwj/s5dBOdjKNWywcTDOJ2ki59BqbvjSqnkl5HsUDLA0lWwlK6D EVplJT2NNY0gHfjZDPiQ7BUlEyjH8qcNtBX/VUR11+68l7Gr6xyBag== X-Google-Smtp-Source: AGHT+IEWyTJYvTQ8G6tSxlCsQ7uD5ydQMJw2gBZOYNudrhjDGhuqX8sdYQfAp1s2YVBSyzK/XuG/jg== X-Received: by 2002:a05:6a20:9f8f:b0:1f1:432:f4a3 with SMTP id adf61e73a8af0-2046a56d06emr6773136637.23.1745742377485; Sun, 27 Apr 2025 01:26:17 -0700 (PDT) Received: from localhost.localdomain ([14.171.43.210]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-73e2549f570sm5900852b3a.0.2025.04.27.01.26.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Apr 2025 01:26:17 -0700 (PDT) From: Nam Tran To: andy@kernel.org Cc: geert@linux-m68k.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, christophe.jaillet@wanadoo.fr, corbet@lwn.net, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, florian.fainelli@broadcom.com, bcm-kernel-feedback-list@broadcom.com, linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org, Nam Tran Subject: [PATCH v8 3/5] docs: ABI: Document LP5812 sysfs interfaces Date: Sun, 27 Apr 2025 15:24:45 +0700 Message-Id: <20250427082447.138359-4-trannamatk@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20250427082447.138359-1-trannamatk@gmail.com> References: <20250427082447.138359-1-trannamatk@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The LP5812 is a 4 =C3=97 3 matrix RGB LED driver with autonomous animation engine control. The driver provides interfaces to configure LED modes manual/autonomous, set PWM/DC values, and manage autonomous animation engines. Signed-off-by: Nam Tran --- .../ABI/testing/sysfs-bus-i2c-devices-lp5812 | 144 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 145 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812 diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812 b/Docum= entation/ABI/testing/sysfs-bus-i2c-devices-lp5812 new file mode 100644 index 000000000000..04689663a643 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812 @@ -0,0 +1,144 @@ +What: /sys/bus/i2c/devices/.../lp5812_chip_setup/device_enable +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Enables or disables the LP5812 device. (RW) + 0 - Disable + 1 - Enable + +What: /sys/bus/i2c/devices/.../lp5812_chip_setup/dev_config +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Configures drive mode and scan order. (RW) + Some valid values: tcmscan:4:0:1:2:3 (default), tcmscan:3:0:1:2, m= ixscan:2:2:0:3, mixscan:3:0:1:2:3 + +What: /sys/bus/i2c/devices/.../lp5812_chip_setup/device_command +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Issues device-level commands. (WO) + Valid values: "update", "start", "stop", "pause", "continue" + +What: /sys/bus/i2c/devices/.../lp5812_chip_setup/sw_reset +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Triggers a software reset of the device. (WO) + 1 - resets device + 0 - does not reset device + +What: /sys/bus/i2c/devices/.../lp5812_chip_setup/fault_clear +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Clears fault status. (WO) + 1 - clears fault status + 0 - does not clear fault status + +What: /sys/bus/i2c/devices/.../lp5812_chip_setup/tsd_config_status +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Report the current thermal shutdown config status. (RO) + +What: /sys/bus/i2c/devices/.../led_/activate +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Activate or deactivate the specified LED channel. (RW) + 1 - Activate + 0 - Deactivate + +What: /sys/bus/i2c/devices/.../led_/mode +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Selects LED operation mode. (RW) + Valid values: "manual", "autonomous" + +What: /sys/bus/i2c/devices/.../led_/manual_dc +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + DC current level in manual mode. (RW) + Valid values: 0 - 255 + +What: /sys/bus/i2c/devices/.../led_/manual_pwm +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + PWM duty cycle in manual mode. (RW) + Valid values: 0 - 255 + +What: /sys/bus/i2c/devices/.../led_/autonomous_dc +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + DC current level used in autonomous mode. (RW) + Valid values: 0 - 255 + +What: /sys/bus/i2c/devices/.../led_/autonomous_dc +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + DC current level used in autonomous mode. (RW) + Valid values: 0 - 255 + +What: /sys/bus/i2c/devices/.../led_/pwm_dimming_scale +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + PWM dimming scale type. (RW) + Valid values: "linear", "exponential" + +What: /sys/bus/i2c/devices/.../led_/pwm_phase_align +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Configures PWM phase alignment. (RW) + Valid values: "forward", "middle", "backward" + +What: /sys/bus/i2c/devices/.../led_/autonomous_animation +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Controls AEU configuration and playback. (RW) + Format: (aeu number):(start pause time):(stop pause time):(playbac= k time) + with aeu number 1, 2, 3; playback time 0 - 15 + +What: /sys/bus/i2c/devices/.../led_/aep_status +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Shows current animation pattern status, value from 0 to 7. (RO) + +What: /sys/bus/i2c/devices/.../led_/auto_pwm_val +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + Shows the pwm value in autonomous mode when pause the animation, v= alue from 0 to 255. (RO) + +What: /sys/bus/i2c/devices/.../led_/lod_lsd +Date: May 2025 +KernelVersion: 6.16 +Contact: Nam Tran +Description: + 0 0 mean no lod and lsd fault detected, 1 1 mean lod and lsd fault= detected (RO) diff --git a/MAINTAINERS b/MAINTAINERS index 091373b01dc2..5d8ee0801c69 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24020,6 +24020,7 @@ TEXAS INSTRUMENTS' LP5812 4X3 MATRIX LED DRIVER M: Nam Tran L: linux-kernel@vger.kernel.org S: Maintained +F: Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812 F: Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml F: drivers/auxdisplay/Kconfig F: drivers/auxdisplay/Makefile --=20 2.25.1 From nobody Fri Dec 19 15:50:24 2025 Received: from mail-pf1-f171.google.com (mail-pf1-f171.google.com [209.85.210.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4B38D24A067; Sun, 27 Apr 2025 08:26:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742386; cv=none; b=jhvg1+NQ5DgVNm+m/BYu5ckCJiTFAFi1zPuxHAmXdcgYY57N9gTc0OS6Qyqez9WDxAymIcYWdgOs1XMP/GExiSVmxSR4V3/fWi9Oq23M4gfkUXWuD+ZgWgDE/mFDAl2f3jwTIQ0rM6gfKEJDWaNHvGP+fNh+puEPxAs5DADRwUA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742386; c=relaxed/simple; bh=Ea/fJvP/2FdD4QRx6RhBWZrz6FDzUvgx1ZYUhh+WzsU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=ZEtdbEueNnjRFZGJjNzJC98IMsg62wNhLvc1DOaLyRtHO34NmoY0nu4wDm5szQelYxhzhl6YObLZlJ2+7j00HG3h7sgh3yVtvx7UfwWmiVrh8BdHJHTyVyLS7fw6/zagX2Z43x+M5DwyOGaNCTv0dDnYlOaNBMY1/CRhAvuwsN4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=YqXFzaxa; arc=none smtp.client-ip=209.85.210.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="YqXFzaxa" Received: by mail-pf1-f171.google.com with SMTP id d2e1a72fcca58-736c1cf75e4so3182875b3a.2; Sun, 27 Apr 2025 01:26:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745742383; x=1746347183; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Bn2eDJZEGPHUnz8R0+iyYHsVW148wSZoydE0VfHZX2w=; b=YqXFzaxaErHNMih21mL/1Za8NmO/79I6iaIL9LVseZPTrCVmMpUXTcEs9AvUIRuNfy i8S0B3Eb4YQaTVdEwUjgUTvK7Q65nIhDGTSmfjjiAAq9+B2ZKI2ugD5KcCrf6UgJdlA0 XrzcvcXmmKXxz9Y+fB6X7NFz8Ghip4HIz+jOdvohsM1goY7Pr+tUPJ548ANVreG5/NQ3 aGlKUvFjiCYSseZpoIJkg2KSfenRRKNwzNrsrA9KWBpz+FuiZVEP8jvsVMHqdpmssu+V UeXI23/BE3NPckGBV6/B7e4f2yI/x63qz+cY1r/agqgEjhCaqDz9vgc2AH+s+J1RjDzJ BDEg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745742383; x=1746347183; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Bn2eDJZEGPHUnz8R0+iyYHsVW148wSZoydE0VfHZX2w=; b=MQdQyNJNDiRu8x6QYosnQ149kt6reo5UgPApo6lpSQ3Hgf3T/Y2lyNDMSYb6tVu3+Q +b/GKVfNMQ8M15Bf66NTnDcyhLIkfFNVXpDNVbZ1nZCMa70LtWXnj/6baqKS/EWHcajC SKZ1kPMwQ5/eYqpAr/qVHEv34eQt6Npug6C/vjXuUcFOieYkYi3Wecu9zwml3wMpwxJ1 dyLQI35GtG48ERL0+U3WjwDFnkmJngzOcJNQHH9SyilDrE8OmNI5XqAdxuXN8F337Po4 PsQYrE2JWN/tVLWhtMnFDb7crQBF0nznmF/EtNHG+UAOWsBTLH9ZNa/wCiC/+m7ekYJU Tg1w== X-Forwarded-Encrypted: i=1; AJvYcCUSsDlRBswj/9Pe9hAuADoWphSa1lAS99k8LLnr6UsBop0+X4QHWRCNMm76rQOUNu4CCFIO5VVqvGDvRy8G@vger.kernel.org, AJvYcCUv0KKt8GNHqyq1Vcy573O0S5U7wdXfArYGPmNymshPwHNTgXGx6NtrqPkcy1yDlaIek2PfTJm/1VY3@vger.kernel.org, AJvYcCWGFsJEYl4IctX2moDGaY+lBsx03xeFZJJnTw2Bbyh9snvrWSakgI1UmO3WUWi4HsS7d2NxSzKIP2zy@vger.kernel.org X-Gm-Message-State: AOJu0YyClDDSn3p0eTiGfiwEU9SLPhWd8EEpeeEpLtWIh6aP8mp0PcMi mHM3HBr2GWg/9SEZkFbk1jmGLDxB9VyAkKnx2n3ytHQLoXz38Qeg X-Gm-Gg: ASbGncvwwHqmFZ7Rxp6AUPXM/iK0GdKliheuGhk5yPp3K1zMxeDWt8558cxXxDOoTY1 kRGhOjhr38ehmzzAEWU9/58tJ2tuP+MfpZ6bcx+3eRad+hlKJLo4iU5Al4faApGa3QVSJeYe8RS iVlceMwTVHZxU70f/ipBXrAV5mDa0E/+cWFwlrSoz8Ivn6dvWgsCPrmUCGSRGM9+Dx+BLDOyU6k lk9PgrkUl4ifRDPUrCF+K1YuKY11JmZa7cw/DPICIxHSZ61sAF+MCQaBx/aXhgAwzBymvp7sVOY UF3A5gga4vL60CNMteMWC6hiAdS9OmdnxrLN0Hf7jmikMCzdT1pc5Q== X-Google-Smtp-Source: AGHT+IFzZAZuflFPkU3MqoWhUEwdg0j/wbO+odL4l1iegaUwoMiDpQ56iI/0hVPdquw4PE+EsQUkfA== X-Received: by 2002:a05:6a00:3e12:b0:736:32d2:aa82 with SMTP id d2e1a72fcca58-73fd8e548b8mr9932987b3a.23.1745742383453; Sun, 27 Apr 2025 01:26:23 -0700 (PDT) Received: from localhost.localdomain ([14.171.43.210]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-73e2549f570sm5900852b3a.0.2025.04.27.01.26.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Apr 2025 01:26:23 -0700 (PDT) From: Nam Tran To: andy@kernel.org Cc: geert@linux-m68k.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, christophe.jaillet@wanadoo.fr, corbet@lwn.net, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, florian.fainelli@broadcom.com, bcm-kernel-feedback-list@broadcom.com, linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org, Nam Tran Subject: [PATCH v8 4/5] docs: auxdisplay: document TI LP5812 RGB LED driver Date: Sun, 27 Apr 2025 15:24:46 +0700 Message-Id: <20250427082447.138359-5-trannamatk@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20250427082447.138359-1-trannamatk@gmail.com> References: <20250427082447.138359-1-trannamatk@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The driver provides sysfs interfaces to control and configure the LP5812 device and its LED channels. The documetation describes the chip's capabilities, sysfs interface, and usage examples. Signed-off-by: Nam Tran --- .../admin-guide/auxdisplay/index.rst | 3 +- .../admin-guide/auxdisplay/lp5812.rst | 79 +++++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 Documentation/admin-guide/auxdisplay/lp5812.rst diff --git a/Documentation/admin-guide/auxdisplay/index.rst b/Documentation= /admin-guide/auxdisplay/index.rst index e466f0595248..5d627f067c77 100644 --- a/Documentation/admin-guide/auxdisplay/index.rst +++ b/Documentation/admin-guide/auxdisplay/index.rst @@ -5,8 +5,9 @@ Auxiliary Display Support .. toctree:: :maxdepth: 1 =20 - ks0108.rst cfag12864b.rst + ks0108.rst + lp5812.rst =20 .. only:: subproject and html =20 diff --git a/Documentation/admin-guide/auxdisplay/lp5812.rst b/Documentatio= n/admin-guide/auxdisplay/lp5812.rst new file mode 100644 index 000000000000..58d716f899d1 --- /dev/null +++ b/Documentation/admin-guide/auxdisplay/lp5812.rst @@ -0,0 +1,79 @@ +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Kernel driver for lp5812 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +* TI/National Semiconductor LP5812 LED Driver +* Datasheet: https://www.ti.com/product/LP5812#tech-docs + +Authors: Jared Zhou + +Description +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The LP5812 is a 4x3 matrix LED driver with support for both manual and +autonomous animation control. It provides features such as: + +- PWM dimming and DC current control +- Slope time configuration +- Autonomous Engine Unit (AEU) for LED animation playback +- Flexible scan and drive mode configuration + +This driver provides sysfs interfaces to control and configure the LP5812 +device and its LED channels. + +Sysfs Interface +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +LP5812 device exposes a chip-level sysfs group: + /sys/bus/i2c/devices//lp5812_chip_setup/ + +The following attributes are available at chip level: + - device_enable: Enable/disable the device (RW) + - dev_config: Configure drive mode and scan order (RW) + - device_command: Issue device-wide commands (WO) + - sw_reset: Reset the hardware (WO) + - fault_clear: Clear any device faults (WO) + - tsd_config_status: Read thermal shutdown config status (RO) + +Each LED channel is exposed as: + /sys/bus/i2c/devices//led_/ + +Each LED exposes the following attributes: + - activate: Activate or deactivate the LED (RW) + - mode: manual or autonomous mode (RW) + - manual_dc: DC current value (0=E2=80=93255) (RW) + - manual_pwm: PWM duty cycle (0=E2=80=93255) (RW) + - autonomous_dc: DC current in autonomous mode (RW) + - pwm_dimming_scale: linear or exponential (RW) + - pwm_phase_align: PWM alignment mode (RW) + - autonomous_animation: Config autonomous animation mode with aeu number= , start pause time, stop pause time, playback time (RW) + - aep_status: autonomous engine pattern status (RO) + - auto_pwm_val: pwm value in autonomous mode when pause the animation (R= O) + - lod_lsd: lod and lsd fault detected status (RO) + +Example Usage +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +To control led_A0 in manual mode:: + echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/lp5812_chip_setup/device_ena= ble + echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/activate + echo manual > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/mode + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/manual_dc + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/manual_pwm + +To control led_A0 in autonomous mode:: + echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/lp5812_chip_setup/device_ena= ble + echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/activate + echo autonomous > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/mode + echo 1:10:10:15 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/autonomous_a= nimation + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm1 + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm2 + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm3 + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm4 + echo 100 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/pwm5 + echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t1 + echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t2 + echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t3 + echo 5 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/slope_time_t4 + echo 1 > /sys/bus/i2c/drivers/lp5812/xxxx/led_A0/AEU1/playback_time + echo start > /sys/bus/i2c/drivers/lp5812/xxxx/lp5812_chip_setup/device= _command diff --git a/MAINTAINERS b/MAINTAINERS index 5d8ee0801c69..dd504dee3274 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -24021,6 +24021,7 @@ M: Nam Tran L: linux-kernel@vger.kernel.org S: Maintained F: Documentation/ABI/testing/sysfs-bus-i2c-devices-lp5812 +F: Documentation/admin-guide/auxdisplay/lp5812.rst F: Documentation/devicetree/bindings/auxdisplay/ti,lp5812.yaml F: drivers/auxdisplay/Kconfig F: drivers/auxdisplay/Makefile --=20 2.25.1 From nobody Fri Dec 19 15:50:24 2025 Received: from mail-pf1-f182.google.com (mail-pf1-f182.google.com [209.85.210.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 49CDB24E4CE; Sun, 27 Apr 2025 08:26:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742393; cv=none; b=JHRwU4nly/+z3W4pJi/khoCXMYr6wJoF4gV8m8q9TgqGSi+1wpI0+J6WDhfiTRfAiWhupxFWPmfLdyaDNfEKTyyho7xJ+vTjmmJs+xyk89Oijcekbx+j2uiq4n92HK8r83cg0mUavdScmTvz162Ax9Disdsz02Hz6ZhC3IYQvHo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1745742393; c=relaxed/simple; bh=n/s2ADo+b0i7nG5dHcMjYTx2QEqnguzqYqE4wCSoiL8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=fS0TAyEiL0CyI4EL3iAdPpmH8OI1M8IbSx4rb8qc9q54BpsCq2893MUU3TnIq/BT/MYg92insK2NTSFpE0r3p9dC9GADL+LlwKU/etlYfQxLoaq83zQAqn1rpm9MPwJIYXovNin/QsLzaVWC1UMiryekeFtV8BI/X/h0glRN3yA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=nbtpjSav; arc=none smtp.client-ip=209.85.210.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="nbtpjSav" Received: by mail-pf1-f182.google.com with SMTP id d2e1a72fcca58-7369ce5d323so2974872b3a.1; Sun, 27 Apr 2025 01:26:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1745742390; x=1746347190; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Tfx8AHr/yIS8tp/7G1Y7pM5wDQkLVkrc9vIVDSWIJkA=; b=nbtpjSavMT/LyqLpcXS1Fa+PnTQXrSRn10gj1DjxvZw4e0RKkRUZjwDK6pdedcKAO8 0mqODOGqXRpEyTVYHCNv+xjy2OyGlPyn8fLKogpi468SIgTI43ZmuOU1vgdYgxqPQDzQ nu+GDZlpA6kOQxtTaLkAs/MW2coFwMqwm/ZkHFDYORUsYwrZMpo/lhfU8liHGXiwKWUE wi51QsZ1hFspa1RbN4iygJ7/WpVZKr99yq0a5Y4+zo4cJ34w8/Xdp9fw71DeCmSV+PN3 pNJpNNq4J1B2yenKzPyxEEoaHI3AKJJcLCdDv+/yln4ukgVZ3In66f3p4831H49h2yav MkcA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1745742390; x=1746347190; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=Tfx8AHr/yIS8tp/7G1Y7pM5wDQkLVkrc9vIVDSWIJkA=; b=o6rVlBaEo/17aRITdBDY3X4KHELxhiA77bbn7+G3+Qj2bzu29An0+42Lz6xzHlKIf5 XJhs2UvXuHS1mrPI9uxNEWAzcFaJnsHRiYcIItmxYoUrtjzIfC9pKye56ZLu8lwsWRb5 9SkF0FcAjWUPYTgCNqc9Li3bmLchX/rn+99WO5P1pmo8cC06ABu+JaEwO9B6KdL/5FT2 27q/GnRhQlhXk/0q6qSIpxxFT0GatrRUi0kqHTrSOGkmx17Mhu60I+pEPZZv0FZ1ulsN gcJRhfQr5aUzK3np3sPlsuroTELF3nyQGM1O1UF+t7k96Wu8rFyrMA0geKcNAiIa9Uxu kESA== X-Forwarded-Encrypted: i=1; AJvYcCUOz6GbNtCjF7zRsNf5n6Fvrk27jeRV1ar48CG98QoNR93dfT8f7WS4yPLnwUM0AW+m7K5nfx8w+VLU@vger.kernel.org, AJvYcCV8fW+3KpdxYs+N/81AmvH6EIker/FFqypajx2xdaARsbTMdOTIVqxE0TExvmm9IUgpyHWVOYkq+VWfkcfN@vger.kernel.org, AJvYcCWJZKzFhxWKlQe6pIWKCmXZaaOQk0HlSaeOiko1/KAS5Jb1C+TAPhSLHxj5uR43Q+CnmgozJPabrF7Y@vger.kernel.org X-Gm-Message-State: AOJu0YwLQNo8X+GWGmc3xKDKSS+3Pvdg1n9uvPtsyR6q00XeEelRJTcw qCqQ6uDwSEMsZehkSGqWItEiWXVwWXSbdeYYJ2ScTyAO46uxC+FK X-Gm-Gg: ASbGncvdsyD8PBM+A2rOlZe84dNTsGsUXrFM2WNEfmjhaq+CaxzSFk8N853MKpn70TD xoeQ6NRf55amWKzFIFUJxczodtC+8vMyYhviG9jTgYFm8naUrP6uzRZI26d1/Kp+KXcu753nghK sCPhOJ1TC0v/7Tc0/T7nVPgaGTGX9TR2NXJ6lau4iwe7ocec4Qcoq5mO4S569Gg29kGDrrYcoRV 97fqeToO0dmfl2/yxGmGaVW0SNNylXjQaSfRmfUql57j2ORxhOwj8+qUzkGT2q1TFsmyRrxYORt PYi3xusJIxWpdssdfOkRaVoVKS1P1x3tX1a7v4oOZr8o1CAjJ7pulQ== X-Google-Smtp-Source: AGHT+IEzt7O6nxfbxNZZfuE1bP+CHD3uFhriBll+5j0uMVFN7BYF4H32/W9H2w3LsDhK6SwCjKCA3w== X-Received: by 2002:a05:6a00:114b:b0:732:2923:b70f with SMTP id d2e1a72fcca58-73fd74c5d0amr10293019b3a.11.1745742390591; Sun, 27 Apr 2025 01:26:30 -0700 (PDT) Received: from localhost.localdomain ([14.171.43.210]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-73e2549f570sm5900852b3a.0.2025.04.27.01.26.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 27 Apr 2025 01:26:30 -0700 (PDT) From: Nam Tran To: andy@kernel.org Cc: geert@linux-m68k.org, robh@kernel.org, krzk+dt@kernel.org, conor+dt@kernel.org, christophe.jaillet@wanadoo.fr, corbet@lwn.net, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, florian.fainelli@broadcom.com, bcm-kernel-feedback-list@broadcom.com, linux-rpi-kernel@lists.infradead.org, linux-arm-kernel@lists.infradead.org, Nam Tran Subject: [PATCH v8 5/5] arm64: dts: Add LP5812 node for Raspberry Pi 4 Model B Date: Sun, 27 Apr 2025 15:24:47 +0700 Message-Id: <20250427082447.138359-6-trannamatk@gmail.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20250427082447.138359-1-trannamatk@gmail.com> References: <20250427082447.138359-1-trannamatk@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add the LP5812 LED driver node to the Device Tree for Raspberry Pi 4 B. This enables the LED connected to the LP5812 to be controlled via I2C. Signed-off-by: Nam Tran --- arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot= /dts/broadcom/bcm2711-rpi-4-b.dts index 353bb50ce542..2f058cf5d76e 100644 --- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts +++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts @@ -152,6 +152,16 @@ &hdmi1 { status =3D "okay"; }; =20 +&i2c1 { + #address-cells =3D <1>; + #size-cells =3D <0>; + + led-controller@1b { + compatible =3D "ti,lp5812"; + reg =3D <0x1b>; + }; +}; + &led_act { gpios =3D <&gpio 42 GPIO_ACTIVE_HIGH>; }; --=20 2.25.1