From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (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 A67334499BB for ; Tue, 21 Jul 2026 09:53:11 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627593; cv=none; b=HisIsFU7dZ7XHIEfWnFZVALTYQwFuuebFHhub2DORvEAEuzO0d5zg+JWVbEqg5ZtuepQWs68KgRc/VdNDKENDabvDKlK72mh4HMTBsDjOpftSdq4QFcGTMQmciNE8e8kl76Hh/T4iHxYrv4A8PKjow08C8QLeUiF0hZpZnMdY7s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627593; c=relaxed/simple; bh=0ir6EEm9FNQCLp9OUbkjQzJH24SRp6mnaINdn/CT2yM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Kppu7ZBOS00LVSSnGjzyDYtM9NtKUkfg6jakZx/w7F6cd0KTNnpA0WKkDCrGPOnQr2vOe57RKrjD8jhtjuspVWK4wKzwHxdgfjj0BtOqtTRLHBkXSuPA1hPBWbAcIUE13F2c8c9kt4bxtpnXdeoV9syzrigbzsXo9WR8/y92fuM= 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=YsOOpwQ+; arc=none smtp.client-ip=209.85.128.53 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="YsOOpwQ+" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-49547fc63a0so19692865e9.2 for ; Tue, 21 Jul 2026 02:53:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627590; x=1785232390; 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:content-type; bh=Y460qay5LS+R/33REWXsFlmhxRCfcVfcAqdFzdNEv44=; b=YsOOpwQ+3wfC0e2UXNFHanFBM/BVD5SaU4flkwj/lb4t6xsTB6dowmnnSHlAMlI3ug KpBbsNrDtTMlF9Jfsyz4WgZRLmN0LqMwTJ0LsnEOznlcIYcJLOIaBx//jwnLFUIKYBhy 5DLHOmTE1xFo8Qnw63/Y4nXb/N6S6RUTVhXrk9WeGlLnhn3Kle/WXVFPN2WubL60GQgr FBYoGiDvedXc7Y+eulEzpLiN43koGYjP2I/W0x1RQSciafRvfTSw3zeqaiF958o43xbz yBNp6Bvq7XKPi5fyqwPqBrjrGnU40gC95XJhKwADxJtgxsHEk7vhYWgIIUNWuT4kxFH+ HBvA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627590; x=1785232390; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=Y460qay5LS+R/33REWXsFlmhxRCfcVfcAqdFzdNEv44=; b=LVGk0EumcUYlQCKfD/agxU2Qio539spE5DLbgqPdgKM+hX/DzgoXTIV4kMiLuz6VQH +1caZqcqfNqy8nQpyB98vIaVAjwvK+Xy8fT+IWvFbZwyrbuL24D60VLGJUqFVS37NniK n/3CwmBWZJgqK7l2AaftyoqTTaNISlVwUhJRKQI8DUU6PQb42x9xNriG4SU1KvPJ3I7Y nMtnCJQvOmtait3MDVyxHjSozud7SMYHGtcqFnOHOLeky+fz/LOcf5DmvZwZINkVSpLS Y+JZ9pFgGfLChfy1Rmt7dpxVXDACRn8krfpRM0u7yILbG0E6z9hWNPOtaO5YHXiX7Qeu Hi0A== X-Forwarded-Encrypted: i=1; AHgh+Rr4Yp0DnIjERyy2oyNP6qa9UwSDSBPVNQB+hcoJvON0WITKk1Zy6KK9PIRaNcmxX3XW6UXmHWjZ9V6ds7I=@vger.kernel.org X-Gm-Message-State: AOJu0Yy7WevXoZUx+f5lhQh+L4SamBkfr6JrQhAfQMc47sINaP1mjZKF 0gb463u1WUxT3WaMkrOHVOHxux+3PHAvqlC0GbgIqLQjOlNVP9H7F2mT X-Gm-Gg: AfdE7ck0LGBykzBbcLSH1ItGbK9f2HUkpNzENCqgIbKlHHlyqMlA1r8zxnv8VP3B0XN mZ0PLJHwBJ3FPn1FD7Gs6mwLOBNInUE/eqxukKwHRboPm8KzqWeW8MhFalksVu7jw71mKyQRbK2 iGRYrNuNEdBTN2mPiY0kHyz4jj+HJJKpqeINvVJ+fM7m1hkh2SFM+23q+I2fm+9Pbhbd7LzoDYd D17d0gvr7aJdOPSUVcXezZRDchjvCMHFhda0sLNGlEZpe9rw9lPNBeE+Vnl3e/4PsamsKziXprF epeAuRErUZ/onP/5StLjukPz4vP1LqiSaQwUMjlLyGDxd3yniMplqjamTGth4hbFr+EjxWf3Cmx Q2AYdRlSnrw81RGlp6LCoIMGj14N548DgZ//zpoHVscBErUqbUvUhz9zO7EB6/WjaXEsWlb8THo sUW5Xm9AvnxFrwJL9M4QEjZ2I= X-Received: by 2002:a05:600c:5490:b0:495:6130:7805 with SMTP id 5b1f17b1804b1-49561307a51mr66115845e9.14.1784627589765; Tue, 21 Jul 2026 02:53:09 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:09 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Date: Tue, 21 Jul 2026 12:52:27 +0300 Message-ID: <20260721095233.420823-2-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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" Document embedded controller used in ASUS Transformer device series. Signed-off-by: Svyatoslav Ryhel Reviewed-by: Rob Herring (Arm) --- .../asus,tf201-ec-pad.yaml | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 Documentation/devicetree/bindings/embedded-controller/a= sus,tf201-ec-pad.yaml diff --git a/Documentation/devicetree/bindings/embedded-controller/asus,tf2= 01-ec-pad.yaml b/Documentation/devicetree/bindings/embedded-controller/asus= ,tf201-ec-pad.yaml new file mode 100644 index 0000000000000..60b6375864aaf --- /dev/null +++ b/Documentation/devicetree/bindings/embedded-controller/asus,tf201-ec-p= ad.yaml @@ -0,0 +1,119 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/embedded-controller/asus,tf201-ec-pad.y= aml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: ASUS Transformer's Embedded Controller + +description: + Several Nuvoton based Embedded Controllers attached to an I2C bus, + running a custom ASUS firmware, specific to the ASUS Transformer + device series. + +maintainers: + - Svyatoslav Ryhel + +properties: + compatible: + description: + The 'pad' suffix is used for the controller within the tablet, while + the 'dock' suffix refers to the controller in the mobile dock keyboa= rd. + oneOf: + - enum: + - asus,sl101-ec-dock + - asus,tf101-ec-dock + - asus,tf201-ec-pad + - asus,tf600t-ec-dock + - asus,tf600t-ec-pad + + - items: + - enum: + - asus,tf101g-ec-dock + - asus,tf201-ec-dock + - asus,tf300t-ec-dock + - asus,tf300tg-ec-dock + - asus,tf300tl-ec-dock + - asus,tf700t-ec-dock + - const: asus,tf101-ec-dock + + - items: + - enum: + - asus,tf300t-ec-pad + - asus,tf300tg-ec-pad + - asus,tf300tl-ec-pad + - asus,tf700t-ec-pad + - const: asus,tf201-ec-pad + + - items: + - enum: + - asus,tf701t-ec-dock + - const: asus,tf600t-ec-dock + + - items: + - enum: + - asus,p1801-t-ec-pad + - asus,tf701t-ec-pad + - const: asus,tf600t-ec-pad + + reg: + description: + The ASUS Transformer EC has a main I2C address and an associated + DockRAM device, which provides power-related functions for the + embedded controller. Both addresses are required for operation. + minItems: 2 + + reg-names: + items: + - const: ec + - const: dockram + + interrupts: + maxItems: 1 + + request-gpios: + maxItems: 1 + +required: + - compatible + - reg + - reg-names + +allOf: + - $ref: /schemas/power/supply/power-supply.yaml + - if: + properties: + compatible: + not: + contains: + const: asus,tf600t-ec-dock + then: + required: + - interrupts + - request-gpios + +unevaluatedProperties: false + +examples: + - | + #include + #include + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + embedded-controller@19 { + compatible =3D "asus,tf201-ec-dock", "asus,tf101-ec-dock"; + reg =3D <0x19>, <0x1b>; + reg-names =3D "ec", "dockram"; + + interrupt-parent =3D <&gpio>; + interrupts =3D <151 IRQ_TYPE_LEVEL_LOW>; + + request-gpios =3D <&gpio 134 GPIO_ACTIVE_LOW>; + + monitored-battery =3D <&dock_battery>; + }; + }; +... --=20 2.53.0 From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f43.google.com (mail-wm1-f43.google.com [209.85.128.43]) (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 58CEB44BCB1 for ; Tue, 21 Jul 2026 09:53:13 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627596; cv=none; b=BTNAD1m4oUgSsMxOeWIdmGlhMrJQxNF0wi+AFCrYfkrdFIgv/5x06Ox3mcnZ8m0qSR8A/ob/X2iGvS3oYnw+HsgH25XeVNmoP9bThzODAoHVdt8IWkruA4Nmwao9wNOdLPdgziwc59KojR7/mOX2kLmOVJFCrNKnW5C30ObU0Ko= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627596; c=relaxed/simple; bh=XKhtCKi3c9ilgUUFRcsvBq4cvPvaOLdrvNzfXyEO7/g=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=qQBRkj41lXu/YLg3xUBi9/n99PmjFOjI5e8QfIHKimU7udZ2izLJBb2abYTDI6yNEmWLt9R+Rq+guLRVunIDSmxvSTahs3N9mmDzLe9/n+Kcz6rHKKuV7E+7aR2C5es0U3mD53qKr2mILZudd6fAm9LAI9thiFObu176QD/QTVk= 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=bw/cAfW+; arc=none smtp.client-ip=209.85.128.43 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="bw/cAfW+" Received: by mail-wm1-f43.google.com with SMTP id 5b1f17b1804b1-495590dde14so22849555e9.0 for ; Tue, 21 Jul 2026 02:53:13 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627591; x=1785232391; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=ml/1O/TpIDXWhoGHFqwDCcScYym3Psk0U4wzLFPGZik=; b=bw/cAfW+YWRGeq9zPhlBlU34AVztL8rDijrV/XPu8ELMAXn9qEP9CktVYXO/o2aamG NgcrKEnTSVtOfvT7o7NAoEsHH+hjoIcG6JzNDS4RjpPTUrB00j2dHpqAL2+/c8LrznqP AKLrwPABsRkupCyVoK5tUNe3XUPJBBhQGdfJN2kW9GfDWoPU2wxUr/XM94dQOkKO0LN2 PEQz1fMF44C9nud4K+dLjhAj+QZGtbR+loT39OtNhBcJRt61knhnlVhh1W5dH09yxdFM O6N2VUX0RP0ytm5YuEb5llq0gd3XBYe8gxUYdQTmF6CbkfzKoh2wQm3Ebha4F1lWMstt jw7w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627591; x=1785232391; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=ml/1O/TpIDXWhoGHFqwDCcScYym3Psk0U4wzLFPGZik=; b=csOriQ06J3BGFMPRQb7hhd9atxh3zkRDCy+AcB13jtgFbti35kWQRo18LQLd6l7lZe jAQdKRkHe3RVngeTxeLzoEhJsCdlMmkd3LaMf2Rt6AdceFsl/S7VL2TPazo/ZU4rbXlS 5nTSaAF5UcIyWEkevxCnUxEZcvSI1rcTgMbLTH6SSaS6AkxT8KLIlbZnJeyjEX2Pb6wJ WELOTWF8r4dDEhbXNSw4lbU+9zGipUaCv5AguOxMSyOt3lb5gQmWZVC/cbFWp9Wium7f QkG15U2Xr52VYX+MdJiCPkc1vAgw3WfWPRWRkFMTYNEuAb/S/GoY28GAxgVPpjiS3i/t vbqQ== X-Forwarded-Encrypted: i=1; AHgh+RrWrFbya9RrhdEFcivLLV6Kt+RwA+R2XeFVg96GE6bhh/b4c61J/uwxQQ7qdz4QPe+BqbIANz+a1ONr9ok=@vger.kernel.org X-Gm-Message-State: AOJu0YwboBPeuFfdKFjD5pnhTTCJzS67+HiFZGZfKC1d+ZdcMf/9RVdA EM6OmzdbF1Mxni7VqiRsADfUHSvyWUhn26J3lWdJ6qpS7LGxQ5x9Iy3A X-Gm-Gg: AfdE7cnv6IY6qGZV4y6cTxAc2odNpULbXgIxiBprIh+vey0t2aJN7e9qHsqWum2mR02 znK6L6ITya4ehQG6YFXfo2T+lvXuOXiQZSVc25v9QyRUPZ2gv5M5uP160h7CX+fmLkEHnp/rjLu DlM4mHYQ08peEfJg/GNU4k4gMe7y8CMcf/mfA4GTCSubTKCkFd4VguwHswt7sDmNyDVaoTjosOq Pa0WTMVSUklusQs3dwx9DkVqy89zYv6dEopf52EWT4vpnlLuEEjypSXe30tVZX6xfjjQ4pm+JW8 4HN8Cr8rShtY/53wa+fYspj7aAsm4vZcyimg7I3jcNd0NUMO7BYC3Sgzk8hRbXqaQcZBlS0da8p BFwzJ0peGO0uzSvZWCTsIJ0hDkVAVNFEsZzxbDkvsWsbZDf5kffzfqocFl9BiA19iNEnfcQ+7xd rxIF72RGyrXa7ozipKiUwtG4vZDOt77uD+lg== X-Received: by 2002:a05:600c:3512:b0:493:cfe8:5b36 with SMTP id 5b1f17b1804b1-4954a3eec4bmr200708205e9.8.1784627591326; Tue, 21 Jul 2026 02:53:11 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:10 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 2/7] mfd: Add driver for ASUS Transformer embedded controller Date: Tue, 21 Jul 2026 12:52:28 +0300 Message-ID: <20260721095233.420823-3-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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 From: Micha=C5=82 Miros=C5=82aw Support Nuvoton NPCE795-based ECs as used in Asus Transformer TF201, TF300T, TF300TG, TF300TL and TF700T pad and dock, as well as TF101 dock and TF600T, P1801-T and TF701T pad. This is a glue driver handling detection and common operations for EC's functions. Co-developed-by: Svyatoslav Ryhel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Micha=C5=82 Miros=C5=82aw --- drivers/mfd/Kconfig | 16 + drivers/mfd/Makefile | 1 + drivers/mfd/asus-transformer-ec.c | 549 ++++++++++++++++++++++++ include/linux/mfd/asus-transformer-ec.h | 92 ++++ 4 files changed, 658 insertions(+) create mode 100644 drivers/mfd/asus-transformer-ec.c create mode 100644 include/linux/mfd/asus-transformer-ec.h diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 35f6e9b76d056..7e67d4e4249ff 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -137,6 +137,22 @@ config MFD_AAT2870_CORE additional drivers must be enabled in order to use the functionality of the device. =20 +config MFD_ASUS_TRANSFORMER_EC + tristate "ASUS Transformer's embedded controller" + select MFD_CORE + depends on I2C && OF + help + Select this to enable support for the Embedded Controller (EC) + found in Tegra based ASUS Transformer series tablets and mobile + docks. + + This driver handles the core I2C communication with the EC and + provides support for its sub-devices, including battery management, + charger detection, LEDs and keyboard dock functions support. + + This driver can also be built as a module. If so, the module + will be called asus-transformer-ec. + config MFD_AT91_USART tristate "AT91 USART Driver" select MFD_CORE diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index dd4bb7e77c336..79806201bbb24 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_MFD_88PM805) +=3D 88pm805.o 88pm80x.o obj-$(CONFIG_MFD_88PM886_PMIC) +=3D 88pm886.o obj-$(CONFIG_MFD_ACT8945A) +=3D act8945a.o obj-$(CONFIG_MFD_SM501) +=3D sm501.o +obj-$(CONFIG_MFD_ASUS_TRANSFORMER_EC) +=3D asus-transformer-ec.o obj-$(CONFIG_ARCH_BCM2835) +=3D bcm2835-pm.o obj-$(CONFIG_MFD_BCM590XX) +=3D bcm590xx.o obj-$(CONFIG_MFD_BD9571MWV) +=3D bd9571mwv.o diff --git a/drivers/mfd/asus-transformer-ec.c b/drivers/mfd/asus-transform= er-ec.c new file mode 100644 index 0000000000000..739c66fdaf22f --- /dev/null +++ b/drivers/mfd/asus-transformer-ec.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUSEC_ACCESS_TIMEOUT 300 +#define ASUSEC_DOCKRAM_OFFSET 2 +#define ASUSEC_ECREQ_DELAY 50 +#define ASUSEC_ECREQ_TIMEOUT 200 +#define ASUSEC_RESET 0 +#define ASUSEC_RETRY_MAX 3 +#define ASUSEC_RSP_BUFFER_SIZE (ASUSEC_ENTRIES / ASUSEC_ENTRY_SIZE) + +enum asusec_variant { + ASUSEC_SL101_DOCK =3D 1, + ASUSEC_TF101_DOCK, + ASUSEC_TF201_PAD, + ASUSEC_TF600T_PAD, + ASUSEC_MAX +}; + +enum asusec_mode { + ASUSEC_MODE_NONE, + ASUSEC_MODE_NORMAL, + ASUSEC_MODE_FACTORY, + ASUSEC_MODE_MAX +}; + +/** + * struct asus_ec_chip_info + * + * @name: prefix associated with the EC + * @variant: id of programming model of EC + * @mode: state of Factory Mode bit in EC control register + */ +struct asus_ec_chip_info { + const char *name; + enum asusec_variant variant; + enum asusec_mode fmode; +}; + +/** + * struct asus_ec_data + * + * @ec: public part shared with all cells (must be first) + * @ecreq_lock: prevents simultaneous access to EC + * @ecreq_gpio: EC request GPIO + * @client: pointer to EC's i2c_client + * @info: pointer to EC's version description + * @ec_buf: buffer for EC read + * @logging_disabled: flag disabling logging on reset events + */ +struct asus_ec_data { + struct asusec_core ec; + struct mutex ecreq_lock; + struct gpio_desc *ecreq_gpio; + struct i2c_client *client; + const struct asus_ec_chip_info *info; + u8 ec_buf[ASUSEC_ENTRY_BUFSIZE]; +}; + +/** + * struct dockram_ec_data + * + * @ctl_lock: prevent simultaneous access to Dockram + * @ctl_buf: buffer for Dockram read + */ +struct dockram_ec_data { + struct mutex ctl_lock; + u8 ctl_buf[ASUSEC_ENTRY_BUFSIZE]; +}; + +/** + * asus_dockram_access_ctl - Read from or write to the DockRAM control reg= ister. + * @client: Handle to the DockRAM device. + * @out: Pointer to a variable where the register value will be stored. + * @mask: Bitmask of bits to be cleared. + * @xor: Bitmask of bits to be set (via XOR). + * + * This performs a control register read if @out is provided and both @mask + * and @xor are zero. Otherwise, it performs a control register update if + * @mask and @xor are provided. + * + * Returns a negative errno code else zero on success. + */ +int asus_dockram_access_ctl(struct i2c_client *client, u64 *out, u64 mask, + u64 xor) +{ + struct dockram_ec_data *ddata =3D i2c_get_clientdata(client); + u8 *buf =3D ddata->ctl_buf; + u64 val; + int ret =3D 0; + + guard(mutex)(&ddata->ctl_lock); + + memset(buf, 0, ASUSEC_ENTRY_BUFSIZE); + ret =3D i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_CONTROL, + ASUSEC_ENTRY_SIZE, buf); + if (ret < ASUSEC_ENTRY_SIZE) { + dev_err(&client->dev, "failed to access control buffer: %d\n", + ret); + return ret < 0 ? ret : -EIO; + } + + if (buf[0] !=3D ASUSEC_CTL_SIZE) { + dev_err(&client->dev, "buffer size exceeds %d: %d\n", + ASUSEC_CTL_SIZE, buf[0]); + return -EPROTO; + } + + val =3D get_unaligned_le64(buf + 1); + + if (out) + *out =3D val; + + if (mask || xor) { + put_unaligned_le64((val & ~mask) ^ xor, buf + 1); + ret =3D i2c_smbus_write_i2c_block_data(client, + ASUSEC_DOCKRAM_CONTROL, + ASUSEC_ENTRY_SIZE, buf); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(asus_dockram_access_ctl); + +static int asus_ec_signal_request(struct asus_ec_data *ddata) +{ + guard(mutex)(&ddata->ecreq_lock); + + gpiod_set_value_cansleep(ddata->ecreq_gpio, 1); + msleep(ASUSEC_ECREQ_DELAY); + + gpiod_set_value_cansleep(ddata->ecreq_gpio, 0); + msleep(ASUSEC_ECREQ_TIMEOUT); + + return 0; +} + +static int asus_ec_log_info(struct asus_ec_data *ddata, unsigned int reg, + const char *name) +{ + struct device *dev =3D &ddata->client->dev; + u8 buf[ASUSEC_ENTRY_BUFSIZE]; + int ret; + + memset(buf, 0, ASUSEC_ENTRY_BUFSIZE); + ret =3D i2c_smbus_read_i2c_block_data(ddata->ec.dockram, reg, + ASUSEC_ENTRY_SIZE, buf); + if (ret < ASUSEC_ENTRY_SIZE) + return ret < 0 ? ret : -EIO; + + if (buf[0] > ASUSEC_ENTRY_SIZE) { + dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n", + ASUSEC_ENTRY_BUFSIZE, buf, ret); + return -EPROTO; + } + + dev_info(dev, "%-14s: %.*s\n", name, buf[0], buf + 1); + + if (!ddata->ec.model) { + ddata->ec.model =3D devm_kasprintf(dev, GFP_KERNEL, "%.*s", + buf[0], buf + 1); + if (!ddata->ec.model) + return -ENOMEM; + } + + return 0; +} + +static int asus_ec_detect(struct asus_ec_data *ddata) +{ + int ret; + + ret =3D asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_MODEL, "Model"); + if (ret) + return ret; + + ret =3D asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_FW, "FW version"); + if (ret) + return ret; + + ret =3D asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_CFGFMT, "Config forma= t"); + if (ret) + return ret; + + ret =3D asus_ec_log_info(ddata, ASUSEC_DOCKRAM_INFO_HW, "HW version"); + if (ret) + return ret; + + ddata->ec.name =3D ddata->info->name; + + return 0; +} + +static int asus_ec_reset(struct asus_ec_data *ddata) +{ + int retry, ret; + + guard(mutex)(&ddata->ecreq_lock); + + for (retry =3D 0; retry < ASUSEC_RETRY_MAX; retry++) { + ret =3D i2c_smbus_write_word_data(ddata->client, ASUSEC_WRITE_BUF, + ASUSEC_RESET); + if (!ret) + return 0; + + msleep(ASUSEC_ACCESS_TIMEOUT); + } + + return ret; +} + +static void asus_ec_clear_buffer(struct asus_ec_data *ddata) +{ + int ret, retry =3D ASUSEC_RSP_BUFFER_SIZE; + + /* + * Read the buffer till we get valid data by checking ASUSEC_OBF_MASK + * of the status byte or till we reach end of the 256 byte buffer. + */ + while (retry--) { + ret =3D i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF, + ASUSEC_ENTRY_SIZE, + ddata->ec_buf); + if (ret < ASUSEC_ENTRY_SIZE) + continue; + + if (ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK) + continue; + + break; + } +} + +static int asus_ec_susb_on_status(struct asus_ec_data *ddata) +{ + u64 flag; + int ret; + + ret =3D asus_dockram_access_ctl(ddata->ec.dockram, &flag, 0, 0); + if (ret) + return ret; + + flag &=3D ASUSEC_CTL_SUSB_MODE; + dev_info(&ddata->client->dev, "EC FW behaviour: %s\n", + flag ? "susb on when receive ec_req" : + "susb on when system wakeup"); + + return 0; +} + +static int asus_ec_set_factory_mode(struct asus_ec_data *ddata, + enum asusec_mode fmode) +{ + dev_info(&ddata->client->dev, "Entering %s mode.\n", + fmode =3D=3D ASUSEC_MODE_FACTORY ? "factory" : "normal"); + + return asus_dockram_access_ctl(ddata->ec.dockram, NULL, + ASUSEC_CTL_FACTORY_MODE, + fmode =3D=3D ASUSEC_MODE_FACTORY ? + ASUSEC_CTL_FACTORY_MODE : 0); +} + +static int asus_ec_init(struct asus_ec_data *ddata) +{ + int ret; + + ret =3D asus_ec_reset(ddata); + if (ret) + goto err_exit; + + asus_ec_clear_buffer(ddata); + + /* Check and inform about EC firmware behavior */ + ret =3D asus_ec_susb_on_status(ddata); + if (ret) + goto err_exit; + + /* Some EC require factory mode to be set normal on each request */ + if (ddata->info->fmode) + ret =3D asus_ec_set_factory_mode(ddata, ddata->info->fmode); + +err_exit: + if (ret) + dev_err(&ddata->client->dev, "failed to access EC: %d\n", ret); + + return ret; +} + +static void asus_ec_handle_smi(struct asus_ec_data *ddata, unsigned int co= de) +{ + switch (code) { + case ASUSEC_SMI_HANDSHAKE: + case ASUSEC_SMI_RESET: + asus_ec_init(ddata); + break; + } +} + +static irqreturn_t asus_ec_interrupt(int irq, void *dev_id) +{ + struct asus_ec_data *ddata =3D dev_id; + unsigned long notify_action; + int ret; + + ret =3D i2c_smbus_read_i2c_block_data(ddata->client, ASUSEC_READ_BUF, + ASUSEC_ENTRY_SIZE, ddata->ec_buf); + if (ret < ASUSEC_ENTRY_SIZE) + return IRQ_NONE; + + /* Check status byte with ASUSEC_OBF_MASK if data is valid */ + ret =3D ddata->ec_buf[ASUSEC_IRQ_STATUS] & ASUSEC_OBF_MASK; + if (!ret) + return IRQ_NONE; + + notify_action =3D ddata->ec_buf[ASUSEC_IRQ_STATUS]; + if (notify_action & ASUSEC_SMI_MASK) { + unsigned int code =3D ddata->ec_buf[ASUSEC_SMI_CODE]; + + asus_ec_handle_smi(ddata, code); + + notify_action |=3D code << 8; + } + + blocking_notifier_call_chain(&ddata->ec.notify_list, + notify_action, ddata->ec_buf); + + return IRQ_HANDLED; +} + +static void asus_ec_release_dockram_dev(void *client) +{ + i2c_unregister_device(client); +} + +static struct i2c_client *devm_asus_dockram_get(struct device *dev) +{ + struct i2c_client *parent =3D to_i2c_client(dev); + struct i2c_client *dockram; + struct dockram_ec_data *ddata; + int ret; + + dockram =3D i2c_new_ancillary_device(parent, "dockram", + parent->addr + ASUSEC_DOCKRAM_OFFSET); + if (IS_ERR(dockram)) + return dockram; + + ret =3D devm_add_action_or_reset(dev, asus_ec_release_dockram_dev, + dockram); + if (ret) + return ERR_PTR(ret); + + ddata =3D devm_kzalloc(&dockram->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return ERR_PTR(-ENOMEM); + + i2c_set_clientdata(dockram, ddata); + mutex_init(&ddata->ctl_lock); + + return dockram; +} + +static const struct mfd_cell asus_ec_sl101_dock_mfd_devices[] =3D { + MFD_CELL_NAME("asus-transformer-ec-kbc"), +}; + +static const struct mfd_cell asus_ec_tf101_dock_mfd_devices[] =3D { + MFD_CELL_BASIC("asus-transformer-ec-battery", NULL, NULL, 0, 1), + MFD_CELL_BASIC("asus-transformer-ec-charger", NULL, NULL, 0, 1), + MFD_CELL_BASIC("asus-transformer-ec-led", NULL, NULL, 0, 1), + MFD_CELL_NAME("asus-transformer-ec-kbc"), + MFD_CELL_NAME("asus-transformer-ec-keys"), +}; + +static const struct mfd_cell asus_ec_tf201_pad_mfd_devices[] =3D { + MFD_CELL_NAME("asus-transformer-ec-battery"), + MFD_CELL_NAME("asus-transformer-ec-led"), +}; + +static const struct mfd_cell asus_ec_tf600t_pad_mfd_devices[] =3D { + MFD_CELL_NAME("asus-transformer-ec-battery"), + MFD_CELL_NAME("asus-transformer-ec-charger"), + MFD_CELL_NAME("asus-transformer-ec-led"), +}; + +static int asus_ec_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct asus_ec_data *ddata; + const struct mfd_cell *cells; + unsigned int num_cells; + unsigned long irqflags; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) + return dev_err_probe(dev, -ENXIO, + "I2C bus is missing required SMBus block mode support\n"); + + ddata =3D devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + ddata->info =3D device_get_match_data(dev); + if (!ddata->info) + return -ENODEV; + + switch (ddata->info->variant) { + case ASUSEC_SL101_DOCK: + cells =3D asus_ec_sl101_dock_mfd_devices; + num_cells =3D ARRAY_SIZE(asus_ec_sl101_dock_mfd_devices); + break; + case ASUSEC_TF101_DOCK: + cells =3D asus_ec_tf101_dock_mfd_devices; + num_cells =3D ARRAY_SIZE(asus_ec_tf101_dock_mfd_devices); + break; + case ASUSEC_TF201_PAD: + cells =3D asus_ec_tf201_pad_mfd_devices; + num_cells =3D ARRAY_SIZE(asus_ec_tf201_pad_mfd_devices); + break; + case ASUSEC_TF600T_PAD: + cells =3D asus_ec_tf600t_pad_mfd_devices; + num_cells =3D ARRAY_SIZE(asus_ec_tf600t_pad_mfd_devices); + break; + default: + return dev_err_probe(dev, -EINVAL, + "unknown device variant %d\n", + ddata->info->variant); + } + + i2c_set_clientdata(client, ddata); + ddata->client =3D client; + + ddata->ec.dockram =3D devm_asus_dockram_get(dev); + if (IS_ERR(ddata->ec.dockram)) + return dev_err_probe(dev, PTR_ERR(ddata->ec.dockram), + "failed to get dockram\n"); + + ddata->ecreq_gpio =3D devm_gpiod_get(dev, "request", GPIOD_OUT_LOW); + if (IS_ERR(ddata->ecreq_gpio)) + return dev_err_probe(dev, PTR_ERR(ddata->ecreq_gpio), + "failed to get EC request GPIO\n"); + + BLOCKING_INIT_NOTIFIER_HEAD(&ddata->ec.notify_list); + mutex_init(&ddata->ecreq_lock); + + asus_ec_signal_request(ddata); + + ret =3D asus_ec_detect(ddata); + if (ret) + return dev_err_probe(dev, ret, "failed to detect EC version\n"); + + ret =3D asus_ec_init(ddata); + if (ret) + return dev_err_probe(dev, ret, "failed to init EC\n"); + + /* + * Systems using device tree should set up interrupt via DTS, + * the rest will use the default low interrupt. + */ + irqflags =3D dev->of_node ? 0 : IRQF_TRIGGER_LOW; + + ret =3D devm_request_threaded_irq(dev, client->irq, NULL, + &asus_ec_interrupt, + IRQF_ONESHOT | irqflags, + client->name, ddata); + if (ret) + return dev_err_probe(dev, ret, "failed to register IRQ\n"); + + /* Parent I2C controller uses DMA, ASUS EC and child devices do not */ + client->dev.coherent_dma_mask =3D 0; + client->dev.dma_mask =3D &client->dev.coherent_dma_mask; + + return devm_mfd_add_devices(dev, 0, cells, num_cells, NULL, 0, NULL); +} + +static const struct asus_ec_chip_info asus_ec_sl101_dock_data =3D { + .name =3D "dock", + .variant =3D ASUSEC_SL101_DOCK, + .fmode =3D ASUSEC_MODE_NONE, +}; + +static const struct asus_ec_chip_info asus_ec_tf101_dock_data =3D { + .name =3D "dock", + .variant =3D ASUSEC_TF101_DOCK, + .fmode =3D ASUSEC_MODE_NONE, +}; + +static const struct asus_ec_chip_info asus_ec_tf201_pad_data =3D { + .name =3D "pad", + .variant =3D ASUSEC_TF201_PAD, + .fmode =3D ASUSEC_MODE_NORMAL, +}; + +static const struct asus_ec_chip_info asus_ec_tf600t_pad_data =3D { + .name =3D "pad", + .variant =3D ASUSEC_TF600T_PAD, + .fmode =3D ASUSEC_MODE_NORMAL, +}; + +static const struct of_device_id asus_ec_match[] =3D { + { + .compatible =3D "asus,sl101-ec-dock", + .data =3D &asus_ec_sl101_dock_data + }, { + .compatible =3D "asus,tf101-ec-dock", + .data =3D &asus_ec_tf101_dock_data + }, { + .compatible =3D "asus,tf201-ec-pad", + .data =3D &asus_ec_tf201_pad_data + }, { + .compatible =3D "asus,tf600t-ec-pad", + .data =3D &asus_ec_tf600t_pad_data + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, asus_ec_match); + +static struct i2c_driver asus_ec_driver =3D { + .driver =3D { + .name =3D "asus-transformer-ec", + .of_match_table =3D asus_ec_match, + }, + .probe =3D asus_ec_probe, +}; +module_i2c_driver(asus_ec_driver); + +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("ASUS Transformer's EC driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/asus-transformer-ec.h b/include/linux/mfd/as= us-transformer-ec.h new file mode 100644 index 0000000000000..1c25c3a18355c --- /dev/null +++ b/include/linux/mfd/asus-transformer-ec.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __MFD_ASUS_TRANSFORMER_EC_H +#define __MFD_ASUS_TRANSFORMER_EC_H + +#include +#include + +#define ASUSEC_ENTRIES 0x100 +#define ASUSEC_ENTRY_SIZE 32 +#define ASUSEC_ENTRY_BUFSIZE (ASUSEC_ENTRY_SIZE + 1) + +struct i2c_client; + +/** + * struct asusec_core - public part shared with all cells + * + * @model: firmware version running on the EC + * @name: prefix associated with the EC + * @dockram: pointer to Dockram's i2c_client + * @notify_list: notify list used by cells + */ +struct asusec_core { + const char *model; + const char *name; + struct i2c_client *dockram; + struct blocking_notifier_head notify_list; +}; + +/* interrupt sources */ +#define ASUSEC_IRQ_STATUS 1 +#define ASUSEC_OBF_MASK BIT(0) +#define ASUSEC_KEY_MASK BIT(2) +#define ASUSEC_KBC_MASK BIT(3) +#define ASUSEC_AUX_MASK BIT(5) +#define ASUSEC_SCI_MASK BIT(6) +#define ASUSEC_SMI_MASK BIT(7) + +/* SMI notification codes */ +#define ASUSEC_SMI_CODE 2 +#define ASUSEC_SMI_POWER_NOTIFY 0x31 /* USB cable plug event */ +#define ASUSEC_SMI_HANDSHAKE 0x50 /* response to ec_req edge */ +#define ASUSEC_SMI_WAKE 0x53 +#define ASUSEC_SMI_RESET 0x5f +#define ASUSEC_SMI_ADAPTER_EVENT 0x60 /* charger to dock plug event */ +#define ASUSEC_SMI_BACKLIGHT_ON 0x63 +#define ASUSEC_SMI_AUDIO_DOCK_IN 0x70 + +#define ASUSEC_SMI_ACTION(code) (ASUSEC_SMI_MASK | ASUSEC_OBF_MASK | \ + (ASUSEC_SMI_##code << 8)) + +/* control register [0x0a] layout */ +#define ASUSEC_CTL_SIZE 8 + +/* + * EC reports power from 40-pin connector in the LSB of the control + * register. The following values have been observed (xor 0x02): + * + * PAD-ec no-plug 0x40 / PAD-ec DOCK 0x20 / DOCK-ec no-plug 0x40 + * PAD-ec AC 0x25 / PAD-ec DOCK+AC 0x24 / DOCK-ec AC 0x25 + * PAD-ec USB 0x45 / PAD-ec DOCK+USB 0x24 / DOCK-ec USB 0x41 + */ + +#define ASUSEC_CTL_DIRECT_POWER_SOURCE BIT_ULL(0) +#define ASUSEC_STAT_CHARGING BIT_ULL(2) +#define ASUSEC_CTL_FULL_POWER_SOURCE BIT_ULL(5) +#define ASUSEC_CTL_SUSB_MODE BIT_ULL(9) +#define ASUSEC_CMD_SUSPEND_S3 BIT_ULL(33) +#define ASUSEC_CTL_TEST_DISCHARGE BIT_ULL(35) +#define ASUSEC_CMD_SUSPEND_INHIBIT BIT_ULL(37) +#define ASUSEC_CTL_FACTORY_MODE BIT_ULL(38) +#define ASUSEC_CTL_KEEP_AWAKE BIT_ULL(39) +#define ASUSEC_CTL_USB_CHARGE BIT_ULL(40) +#define ASUSEC_CTL_LED_BLINK BIT_ULL(40) +#define ASUSEC_CTL_LED_AMBER BIT_ULL(41) +#define ASUSEC_CTL_LED_GREEN BIT_ULL(42) +#define ASUSEC_CMD_SWITCH_HDMI BIT_ULL(56) +#define ASUSEC_CMD_WIN_SHUTDOWN BIT_ULL(62) + +#define ASUSEC_DOCKRAM_INFO_MODEL 0x01 +#define ASUSEC_DOCKRAM_INFO_FW 0x02 +#define ASUSEC_DOCKRAM_INFO_CFGFMT 0x03 +#define ASUSEC_DOCKRAM_INFO_HW 0x04 +#define ASUSEC_DOCKRAM_CONTROL 0x0a +#define ASUSEC_DOCKRAM_BATT_CTL 0x14 + +#define ASUSEC_WRITE_BUF 0x64 +#define ASUSEC_READ_BUF 0x6a + +int asus_dockram_access_ctl(struct i2c_client *client, + u64 *out, u64 mask, u64 xor); + +#endif /* __MFD_ASUS_TRANSFORMER_EC_H */ --=20 2.53.0 From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (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 00BFA44C659 for ; Tue, 21 Jul 2026 09:53:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627597; cv=none; b=ogWgxEvgx1fPnjcI+aRaNeLoC6Za8UPoTamQdf/oGpNb7564qjDZnN38v1Afj7XSXscT5puHG6L945YA5eQQaQjPsWYmx3ahx0UOd1r1njL9/Ef083DBIN+MeeyNL/ThSfzqCdij9vuyL6yai9rW7gOwgqnq5b7vwDxqAznO5bQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627597; c=relaxed/simple; bh=WKmMyVJmmm3/p6J2LsRvBrQbLiDxoyq8N/mTapyKyU0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=MghwQ5SbWJmYzVxwe5rNymFcsp52zw8MOPgFmtZMXaE2QgJFFKxnTE81yRyzBH++j90WRi7t9f9RxmPdxab/wh4o1v3YqVZ8TYi2+afWAfMf7tf4DawKv7GHRdeogNHrsrWkExsfaMiNXSq3+tyE7QCV7fhuSGsJ9VId0tE1E2I= 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=M4lAs18R; arc=none smtp.client-ip=209.85.128.49 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="M4lAs18R" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-4956869750eso2235475e9.2 for ; Tue, 21 Jul 2026 02:53:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627593; x=1785232393; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=dzLAVPMJ+xd48Qvd/jRYNAR8Ds6btdovTB3v7WFVom8=; b=M4lAs18RiWvtV9SeMgYjGewLh0gy1LfEnLF5SKHnmc30b1aYclJzcrGHFWC+WpMmEZ X6wldj6KoPlKqojAxTXhFaK5fXMHIUvda+XuhwCUWBXap9p4IGOhr4zV7r/WEgPAq4x3 tnsohwkA6yCxn7NMKFQ/DN/7eFJqbyuIrkSWJqsYN961KrIwIrRN3B/vze7zymRUyerv eafgkSShyCXpJvnL3K8bfvf1iGxjGlVd+yaIfRWou8w4t1S08VSAYDxv2ys27EGoRPxC a1/4GTZFXKop/QgD9e3gRWQdUz2+5/xxE899CAwD1a1pYKnFil7D64hpSWJgjF5w68B6 9axw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627593; x=1785232393; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=dzLAVPMJ+xd48Qvd/jRYNAR8Ds6btdovTB3v7WFVom8=; b=XZJxdmGS0ttZrraAdmFjd1iAHTUS9c4js9uOZoXj8RueEGaQURYhO+RAlRdLXLxvR/ Db1wNEji5zadfjYeYrQnD9w4bNHk551BjEvDiJjRs2Fwefl3nO3bm09zlZ1huxVMx6p8 8w/QDR+mPvPFUFQGOLDaUB7Aj4xp6BbDXsx7kqaWSUbwsv1Yvw1nPErvLuIeYQ/Q8hmN 43DbiWhhyX3XRZbdaD5UxnvRLgnXh/1FEdWZMAHK3Q8gEcutKX96MnV79CjfJmhL5ree ZmvRp1WpDt7rljHs5G4bahKuZ9M0esrQO+7nYizVQkDr6o13ZYRyabmueNFwpwBel0z/ CJPQ== X-Forwarded-Encrypted: i=1; AHgh+RqTHbj4euRQoJBQL/E36s+iykb0GZ7PyP+OdJbjeO+S5RGoaqEMUun115Ykrf9QolBLffIgJ3IqWFNdl3E=@vger.kernel.org X-Gm-Message-State: AOJu0YzS442I68ooqTaB1/DmhGcpnQCgcph1mTO4Wr0nahhz7J7ajxYA wAtGepaRf/F9k5CRH0aa6mbO9jXvowEBC3lFvY0RQF2tGJJ5Bsg+eu4L X-Gm-Gg: AfdE7clsj4DjUdyyVVCXzgBdbcJNYC8+5vFx2o0dLw2cov4kcE4l8yZ1gLTHa8389i1 e4xtM9lyeR6m5jMCautxsdPGeUnTCxlny4YkfphP1BBJr31XHpZLcrC7ONZJYzStJaE5Q4YTVt+ J2tiYk3XllFy567y/t2URAOLfGhw/3fT1sZ9MWsDMBl5WuIXgzknA6OdlHyrvp1GWCeYYMYIJmD DC58Z3lqJPltl5iM15iBy+ItnMBjM3FKo+jDHgVoIn5DR1RcqEqaWnVgaNsadYN/lEniKVoOtXA rrZXPL7qsynVwWwfekqfyXbDV6w/WlfBc3aiAdG/U/7WojZC4qDTnpERppRPknbWWuvdLVkAZ1i O/AZ9daZWWE+h190lfzPVTt0w5xIAKnh9goKAh26lqqyIFhvHM+vwT60MRLanfgFqDy28BZHUgi QTVm7aSzWZ3Qc40lrkSd62BME= X-Received: by 2002:a05:600c:19c8:b0:495:6134:6d61 with SMTP id 5b1f17b1804b1-4956202554emr66612155e9.19.1784627592871; Tue, 21 Jul 2026 02:53:12 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:12 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Date: Tue, 21 Jul 2026 12:52:29 +0300 Message-ID: <20260721095233.420823-4-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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 From: Micha=C5=82 Miros=C5=82aw Add input driver for ASUS Transformer dock keyboard and touchpad. Some keys in ASUS Dock report keycodes that don't make sense according to their position, this patch modifies the incoming data that is sent to serio to send proper scancodes. Co-developed-by: Ion Agorria Signed-off-by: Ion Agorria Signed-off-by: Micha=C5=82 Miros=C5=82aw Signed-off-by: Svyatoslav Ryhel --- drivers/input/serio/Kconfig | 15 ++ drivers/input/serio/Makefile | 1 + drivers/input/serio/asus-transformer-ec-kbc.c | 168 ++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 drivers/input/serio/asus-transformer-ec-kbc.c diff --git a/drivers/input/serio/Kconfig b/drivers/input/serio/Kconfig index bacab1f584009..efb5aa59c364b 100644 --- a/drivers/input/serio/Kconfig +++ b/drivers/input/serio/Kconfig @@ -84,6 +84,21 @@ config SERIO_RPCKBD To compile this driver as a module, choose M here: the module will be called rpckbd. =20 +config SERIO_ASUS_TRANSFORMER_EC + tristate "Asus Transformer's Dock keyboard and touchpad controller" + depends on MFD_ASUS_TRANSFORMER_EC + help + Say Y here if you want to use the keyboard and/or touchpad on + Asus Transformed's Mobile Dock. + + For keyboard support you also need atkbd driver. + + For touchpad support you also need psmouse driver with Elantech + touchpad option enabled. + + To compile this driver as a module, choose M here: the module will + be called asus-transformer-ec-kbc. + config SERIO_AMBAKMI tristate "AMBA KMI keyboard controller" depends on ARM_AMBA diff --git a/drivers/input/serio/Makefile b/drivers/input/serio/Makefile index 7f5137dc02e26..bed2a7a92a13d 100644 --- a/drivers/input/serio/Makefile +++ b/drivers/input/serio/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SERIO_SERPORT) +=3D serport.o obj-$(CONFIG_SERIO_RPCKBD) +=3D rpckbd.o obj-$(CONFIG_SERIO_SA1111) +=3D sa1111ps2.o obj-$(CONFIG_SERIO_AMBAKMI) +=3D ambakmi.o +obj-$(CONFIG_SERIO_ASUS_TRANSFORMER_EC) +=3D asus-transformer-ec-kbc.o obj-$(CONFIG_SERIO_Q40KBD) +=3D q40kbd.o obj-$(CONFIG_SERIO_GSCPS2) +=3D gscps2.o obj-$(CONFIG_HP_SDC) +=3D hp_sdc.o diff --git a/drivers/input/serio/asus-transformer-ec-kbc.c b/drivers/input/= serio/asus-transformer-ec-kbc.c new file mode 100644 index 0000000000000..c6d12c44449d0 --- /dev/null +++ b/drivers/input/serio/asus-transformer-ec-kbc.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include + +struct asus_ec_kbc_data { + struct notifier_block nb; + struct asusec_core *ec; + struct i2c_client *parent; + struct serio *sdev[2]; +}; + +static int asus_ec_kbc_notify(struct notifier_block *nb, + unsigned long action, void *data_) +{ + struct asus_ec_kbc_data *priv =3D container_of(nb, struct asus_ec_kbc_dat= a, nb); + unsigned int port_idx, n; + u8 *data =3D data_; + + if (action & (ASUSEC_SMI_MASK | ASUSEC_SCI_MASK)) + return NOTIFY_DONE; + else if (action & ASUSEC_AUX_MASK) + port_idx =3D 1; + else if (action & (ASUSEC_KBC_MASK | ASUSEC_KEY_MASK)) + port_idx =3D 0; + else + return NOTIFY_DONE; + + /* + * The data[0] is the length of the packet including itself. The data[] + * buffer has to be at least 3 bytes (length + ctrl + 1 data byte) and + * must not exceed the EC entry size. + */ + if (data[0] < 2 || data[0] > ASUSEC_ENTRY_SIZE) + return NOTIFY_BAD; + + n =3D data[0] - 2; + data +=3D 2; + + if (port_idx =3D=3D 0) { + /* + * Remap keyboard key codes to match AT layout: + * SEARCH: RIGHT-META [E0 27] -> LEFT-ALT [11] + * MENU: COMPOSE [E0 2F] -> RIGHT-META [E0 27] + */ + if ((n =3D=3D 2 || (n =3D=3D 3 && data[1] =3D=3D 0xF0)) && data[0] =3D= =3D 0xE0) { + u8 *keycode =3D &data[n - 1]; + + switch (*keycode) { + case 0x27: + *keycode =3D 0x11; + ++data; + --n; + break; + case 0x2F: + *keycode =3D 0x27; + break; + } + } + } + + while (n--) + serio_interrupt(priv->sdev[port_idx], *data++, 0); + + return NOTIFY_OK; +} + +static int asus_ec_serio_write(struct serio *port, unsigned char data) +{ + struct asus_ec_kbc_data *priv =3D port->port_data; + + return i2c_smbus_write_word_data(priv->parent, ASUSEC_WRITE_BUF, + (data << 8) | port->id.extra); +} + +static void asus_ec_serio_remove(void *data) +{ + serio_unregister_port(data); +} + +static int asus_ec_register_serio(struct platform_device *pdev, int idx, + const char *name, int cmd) +{ + struct asus_ec_kbc_data *priv =3D platform_get_drvdata(pdev); + struct i2c_client *parent =3D priv->parent; + struct serio *port =3D kzalloc_obj(*port); + + if (!port) + return -ENOMEM; + + priv->sdev[idx] =3D port; + port->dev.parent =3D &pdev->dev; + port->id.type =3D SERIO_8042; + port->id.extra =3D cmd & 0xFF; + port->write =3D asus_ec_serio_write; + port->port_data =3D (void *)priv; + snprintf(port->name, sizeof(port->name), "%s %s", + priv->ec->model, name); + snprintf(port->phys, sizeof(port->phys), "i2c-%u-%04x/serio%d", + i2c_adapter_id(parent->adapter), parent->addr, idx); + + serio_register_port(port); + + return devm_add_action_or_reset(&pdev->dev, asus_ec_serio_remove, port); +} + +static void asus_ec_notifier_chain_unregister(void *data) +{ + struct asus_ec_kbc_data *priv =3D data; + struct asusec_core *ec =3D priv->ec; + + blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb); +} + +static int asus_ec_kbc_probe(struct platform_device *pdev) +{ + struct asusec_core *ec =3D dev_get_drvdata(pdev->dev.parent); + struct asus_ec_kbc_data *priv; + int error; + + priv =3D devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + priv->ec =3D ec; + priv->parent =3D to_i2c_client(pdev->dev.parent); + + priv->nb.notifier_call =3D asus_ec_kbc_notify; + + error =3D blocking_notifier_chain_register(&ec->notify_list, &priv->nb); + if (error) + return dev_err_probe(&pdev->dev, error, + "failed to register blocking notifier chain"); + + error =3D devm_add_action_or_reset(&pdev->dev, + asus_ec_notifier_chain_unregister, + priv); + if (error) + return error; + + error =3D asus_ec_register_serio(pdev, 0, "Keyboard", 0); + if (error) + return error; + + error =3D asus_ec_register_serio(pdev, 1, "Touchpad", I8042_CMD_AUX_SEND); + if (error) + return error; + + return 0; +} + +static struct platform_driver asus_ec_kbc_driver =3D { + .driver.name =3D "asus-transformer-ec-kbc", + .probe =3D asus_ec_kbc_probe, +}; +module_platform_driver(asus_ec_kbc_driver); + +MODULE_ALIAS("platform:asus-transformer-ec-kbc"); +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_DESCRIPTION("ASUS Transformer's Dock keyboard and touchpad driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f53.google.com (mail-wm1-f53.google.com [209.85.128.53]) (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 A4B1944D01C for ; Tue, 21 Jul 2026 09:53:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627603; cv=none; b=AK6nzu6XbokbSI6xjRQPjjU+dGG6cdr0NU5KZqXngVYgHNh0H19XRN8RrQySU9Y2EhXOD7K2LVljKBHA0Gn0Q5ZJd1+lt037zrRDBdDbjENyynJsOXnxUEbUWKPW7XjLRdfiAV5FISYex52kWdWL+IRP0xVjBIjtG3ty4PYSlww= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627603; c=relaxed/simple; bh=36Fm0XKKpglJqJTQXDjBBwGW+jY6X8re7o5+BQh56H8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=uRrnshZuB9hFAmraxtcVh/Q49EzShs7y0VJA2Y76dwtHGYICW4QnIYX5wXzJiWRZ+SwUT8JOgn7HuxKlqm7QblOt+9W4QOdEYiQbLRniF9lsMZ9HOGKAAoiY+wVYg+jF4KVnPOZBlVK9lEsDwgRPQwX0yAcwDyswkIOy1tYXAzk= 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=YCb7jdFu; arc=none smtp.client-ip=209.85.128.53 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="YCb7jdFu" Received: by mail-wm1-f53.google.com with SMTP id 5b1f17b1804b1-495635a85d2so12353625e9.0 for ; Tue, 21 Jul 2026 02:53:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627595; x=1785232395; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=2YqtUSJZQXU32v22oGNWv7m7+Rsi7u5BUjEL4bNYMHE=; b=YCb7jdFuNDOVUtPQRTsRsUs9xx4PhwXgmg/oVorOt43VStdNb48t6hm+7f4yqUGox2 gfj5kI1JxlbK/8UUz5CbK/PrvUHbtb3iczt/MX04emoxsvmeY8EKUzkVus7WSMgsl+qL t5zOQ4ex+uiJ4/ZgoHQ+cB0NU2IJvar8AbB0IrVuEe2UYztUKF/aDk/ldph4K1nPfIp6 VbHyr8v3O3iUk2hpaqL/mreEq78EaMP/hyaLkE7njvJQIfTSLkVzB6KSISXuW3hluv9p rNE2bhmib/Ejg1j+UOHSOsPjnzuWNlAXMsmbyAo6oodrrZPrqL4++u/Dxjutw7pNehuC +pAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627595; x=1785232395; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=2YqtUSJZQXU32v22oGNWv7m7+Rsi7u5BUjEL4bNYMHE=; b=eM3POm23uik9PWLyNVHg0b2tjxwLxMAX2Uw+wmfuElCWkIuuBGz6ft0j4LlsJncS5k NJGtFFFMppHMshq5JzCeGpp4HsUhYrg0QuZxmQ9Zyuyu4xiF1bH2gHRbyxfYEEHfkIeQ g40KN8c3W1IBIkAC2/OLA2+ebQ0YgD1WFt15xI+6ixm1gM7H2d8vgzWQG9v2DkQMzPZe 9bURNduf9R9BDU0olQKKP+EeHompnt4Xwibp6Qnr6vDV+7REvqXZvqMOkO0dm/T26pHo lOvluxmp8TJeqQrQW1Vs6uYVkJv263OhQg/6iPKe/YKcoJv6IL+gEoJ9JpokF9o0ZUrY Pjrg== X-Forwarded-Encrypted: i=1; AHgh+Rr1Z9sE3XrZflTRN+WDvCMdq8EsO/Gs4ZLFSflBK0h4l34jh2seKG2x2SQFAESfZ60KCl4IN2NllqAYAC8=@vger.kernel.org X-Gm-Message-State: AOJu0YwTcLduv2f6OsAea1/DAuqOwU1eKzBIBgk23+tnezxCn++61cq8 t7cTyQ+lQW/dWSvxTywMhuciM5rLk3Ga+RJPLotKBlIk/KL7/xrxBZsl X-Gm-Gg: AfdE7ckn08QlCjMmnxhnLkJE+TeWFkagw7Fr4GHHMdB9sHiwocTPVJi2WpEtr++2qpf +41xv82hR45gcioGmPvsMgLaBNNTPb75gVzYR31fuNfE7C60JKB94qjQr+t7hBQ8c3N+wHxYgLF IqvuUZDvuC1k5EDfoQtXoyt89E5dZzzWQ3CpUZ5RjAxeRxajsQE2cyKIgZHIu2DObWmBJ0AFH3H lg0d+y/dgBqGxkHA0KofTfV3ADlz2d4hP2ADDz9xTX761HoCdKRBWCj49HNtxaSFYa0N7bDY3O5 1yHMrr2kxZLxz8Cxq3EGxY1MsCy8rDeGwTkqzzj5rvcN8M2bfn3JcU6aAtI5WA09RtwMt8YMC6k dNV8dE3z2IpFt5BzSHvQ/npnTk7i//0jBCLpAOisaoxxMVKE47S4jvSy5WpNCvA+t4R5dFHQXan DXJJZGbynrH7lK0gGEYxHkpNI= X-Received: by 2002:a05:600c:b85:b0:495:5365:c0d1 with SMTP id 5b1f17b1804b1-49553d85ea0mr149031905e9.14.1784627594424; Tue, 21 Jul 2026 02:53:14 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:13 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Date: Tue, 21 Jul 2026 12:52:30 +0300 Message-ID: <20260721095233.420823-5-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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 From: Micha=C5=82 Miros=C5=82aw Add support for multimedia top button row of ASUS Transformer's Mobile Dock keyboard. Driver is made that function keys (F1-F12) are used by default which suits average Linux use better and with pressing ScreenLock + AltGr function keys layout is switched to multimedia keys. Only Dock keyboard input events are tracked for AltGr pressing. Co-developed-by: Ion Agorria Signed-off-by: Ion Agorria Signed-off-by: Micha=C5=82 Miros=C5=82aw Signed-off-by: Svyatoslav Ryhel --- drivers/input/keyboard/Kconfig | 10 + drivers/input/keyboard/Makefile | 1 + .../input/keyboard/asus-transformer-ec-keys.c | 310 ++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 drivers/input/keyboard/asus-transformer-ec-keys.c diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 9d1019ba0245b..913cb4900565c 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -89,6 +89,16 @@ config KEYBOARD_APPLESPI To compile this driver as a module, choose M here: the module will be called applespi. =20 +config KEYBOARD_ASUS_TRANSFORMER_EC + tristate "Asus Transformer's Mobile Dock multimedia keys" + depends on MFD_ASUS_TRANSFORMER_EC + help + Say Y here if you want to use multimedia keys present on Asus + Transformer's Mobile Dock. + + To compile this driver as a module, choose M here: the + module will be called asus-transformer-ec-keys. + config KEYBOARD_ATARI tristate "Atari keyboard" depends on ATARI diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makef= ile index 60bb7baf802f7..0d81096887ada 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_KEYBOARD_ADP5585) +=3D adp5585-keys.o obj-$(CONFIG_KEYBOARD_ADP5588) +=3D adp5588-keys.o obj-$(CONFIG_KEYBOARD_AMIGA) +=3D amikbd.o obj-$(CONFIG_KEYBOARD_APPLESPI) +=3D applespi.o +obj-$(CONFIG_KEYBOARD_ASUS_TRANSFORMER_EC) +=3D asus-transformer-ec-keys.o obj-$(CONFIG_KEYBOARD_ATARI) +=3D atakbd.o obj-$(CONFIG_KEYBOARD_ATKBD) +=3D atkbd.o obj-$(CONFIG_KEYBOARD_BCM) +=3D bcm-keypad.o diff --git a/drivers/input/keyboard/asus-transformer-ec-keys.c b/drivers/in= put/keyboard/asus-transformer-ec-keys.c new file mode 100644 index 0000000000000..44235e4a7f548 --- /dev/null +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUSEC_EXT_KEY_CODES 0x20 + +struct asus_ec_keys_data { + struct notifier_block nb; + struct asusec_core *ec; + struct input_dev *xidev; + struct input_handler input_handler; + unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2]; + const char *kbc_phys; + bool special_key_pressed; + bool special_key_mode; +}; + +static void asus_ec_input_event(struct input_handle *handle, + unsigned int event_type, + unsigned int event_code, int value) +{ + struct asus_ec_keys_data *priv =3D handle->handler->private; + + /* Store special key state */ + if (event_type =3D=3D EV_KEY && event_code =3D=3D KEY_RIGHTALT) + priv->special_key_pressed =3D !!value; +} + +static int asus_ec_input_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct asus_ec_keys_data *priv =3D handler->private; + struct input_handle *handle; + int error; + + if (!dev->phys || !strstr(dev->phys, priv->kbc_phys)) + return -ENODEV; + + handle =3D kzalloc_obj(*handle); + if (!handle) + return -ENOMEM; + + handle->dev =3D dev; + handle->handler =3D handler; + handle->name =3D handler->name; + + error =3D input_register_handle(handle); + if (error) + goto err_free_handle; + + error =3D input_open_device(handle); + if (error) + goto err_unregister_handle; + + return 0; + + err_unregister_handle: + input_unregister_handle(handle); + err_free_handle: + kfree(handle); + + return error; +} + +static void asus_ec_input_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id asus_ec_input_ids[] =3D { + { + .flags =3D INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit =3D { BIT_MASK(EV_KEY) }, + }, + { } +}; + +static const unsigned short asus_ec_dock_ext_keys[] =3D { + /* Function keys [0x00 - 0x19] */ + [0x01] =3D KEY_DELETE, + [0x02] =3D KEY_F1, + [0x03] =3D KEY_F2, + [0x04] =3D KEY_F3, + [0x05] =3D KEY_F4, + [0x06] =3D KEY_F5, + [0x07] =3D KEY_F6, + [0x08] =3D KEY_F7, + [0x10] =3D KEY_F8, + [0x11] =3D KEY_F9, + [0x12] =3D KEY_F10, + [0x13] =3D KEY_F11, + [0x14] =3D KEY_F12, + [0x15] =3D KEY_MUTE, + [0x16] =3D KEY_VOLUMEDOWN, + [0x17] =3D KEY_VOLUMEUP, + /* Multimedia keys [0x20 - 0x39] */ + [0x21] =3D KEY_SCREENLOCK, + [0x22] =3D KEY_WLAN, + [0x23] =3D KEY_BLUETOOTH, + [0x24] =3D KEY_TOUCHPAD_TOGGLE, + [0x25] =3D KEY_BRIGHTNESSDOWN, + [0x26] =3D KEY_BRIGHTNESSUP, + [0x27] =3D KEY_BRIGHTNESS_AUTO, + [0x28] =3D KEY_PRINT, + [0x30] =3D KEY_WWW, + [0x31] =3D KEY_CONFIG, + [0x32] =3D KEY_PREVIOUSSONG, + [0x33] =3D KEY_PLAYPAUSE, + [0x34] =3D KEY_NEXTSONG, + [0x35] =3D KEY_MUTE, + [0x36] =3D KEY_VOLUMEDOWN, + [0x37] =3D KEY_VOLUMEUP, +}; + +static void asus_ec_keys_report_key(struct input_dev *dev, unsigned int co= de, + unsigned int key, bool value) +{ + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, key, value); + input_sync(dev); +} + +static int asus_ec_keys_process_key(struct input_dev *dev, u8 code) +{ + struct asus_ec_keys_data *priv =3D dev_get_drvdata(dev->dev.parent); + unsigned int key =3D 0; + + if (code =3D=3D 0) + return NOTIFY_DONE; + + /* Flip special key mode state when pressing SCREEN LOCK + R ALT */ + if (priv->special_key_pressed && code =3D=3D 1) { + priv->special_key_mode =3D !priv->special_key_mode; + return NOTIFY_DONE; + } + + /* + * Relocate code to second "page" if pressed state XOR's mode state + * This way special key will invert the current mode + */ + if (priv->special_key_mode ^ priv->special_key_pressed) + code +=3D ASUSEC_EXT_KEY_CODES; + + if (code < dev->keycodemax) { + unsigned short *map =3D dev->keycode; + + key =3D map[code]; + } + + if (!key) + key =3D KEY_UNKNOWN; + + asus_ec_keys_report_key(dev, code, key, 1); + asus_ec_keys_report_key(dev, code, key, 0); + + return NOTIFY_OK; +} + +static int asus_ec_keys_notify(struct notifier_block *nb, + unsigned long action, void *data_) +{ + struct asus_ec_keys_data *priv =3D + container_of(nb, struct asus_ec_keys_data, nb); + u8 *data =3D data_; + + if (action & ASUSEC_SMI_MASK) + return NOTIFY_DONE; + + if (action & ASUSEC_SCI_MASK) + return asus_ec_keys_process_key(priv->xidev, data[2]); + + return NOTIFY_DONE; +} + +static void asus_ec_keys_setup_keymap(struct asus_ec_keys_data *priv) +{ + struct input_dev *dev =3D priv->xidev; + unsigned int i; + + BUILD_BUG_ON(ARRAY_SIZE(priv->keymap) < ARRAY_SIZE(asus_ec_dock_ext_keys)= ); + + dev->keycode =3D priv->keymap; + dev->keycodesize =3D sizeof(*priv->keymap); + dev->keycodemax =3D ARRAY_SIZE(priv->keymap); + + input_set_capability(dev, EV_MSC, MSC_SCAN); + input_set_capability(dev, EV_KEY, KEY_UNKNOWN); + + for (i =3D 0; i < ARRAY_SIZE(asus_ec_dock_ext_keys); i++) { + unsigned int code =3D asus_ec_dock_ext_keys[i]; + + if (!code) + continue; + + __set_bit(code, dev->keybit); + priv->keymap[i] =3D code; + } +} + +static int asus_ec_keys_register_handler(struct device *dev, + struct asus_ec_keys_data *priv) +{ + struct i2c_client *parent =3D to_i2c_client(dev->parent); + int error; + + priv->input_handler.event =3D asus_ec_input_event; + priv->input_handler.connect =3D asus_ec_input_connect; + priv->input_handler.disconnect =3D asus_ec_input_disconnect; + priv->input_handler.id_table =3D asus_ec_input_ids; + priv->input_handler.passive_observer =3D true; + priv->input_handler.private =3D priv; + priv->input_handler.name =3D devm_kasprintf(dev, GFP_KERNEL, + "%s-media-handler", + priv->ec->name); + if (!priv->input_handler.name) + return -ENOMEM; + + priv->kbc_phys =3D devm_kasprintf(dev, GFP_KERNEL, "i2c-%u-%04x/serio0", + i2c_adapter_id(parent->adapter), + parent->addr); + if (!priv->kbc_phys) + return -ENOMEM; + + error =3D input_register_handler(&priv->input_handler); + if (error) + return error; + + return 0; +} + +static int asus_ec_keys_probe(struct platform_device *pdev) +{ + struct i2c_client *parent =3D to_i2c_client(pdev->dev.parent); + struct asusec_core *ec =3D dev_get_drvdata(pdev->dev.parent); + struct device *dev =3D &pdev->dev; + struct asus_ec_keys_data *priv; + int error; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + priv->ec =3D ec; + + priv->xidev =3D devm_input_allocate_device(dev); + if (!priv->xidev) + return -ENOMEM; + + priv->xidev->name =3D devm_kasprintf(dev, GFP_KERNEL, "%s Keyboard Ext", + ec->model); + priv->xidev->phys =3D devm_kasprintf(dev, GFP_KERNEL, "i2c-%u-%04x", + i2c_adapter_id(parent->adapter), + parent->addr); + + if (!priv->xidev->name || !priv->xidev->phys) + return -ENOMEM; + + asus_ec_keys_setup_keymap(priv); + + error =3D input_register_device(priv->xidev); + if (error) + return dev_err_probe(dev, error, + "failed to register extension keys\n"); + + error =3D asus_ec_keys_register_handler(dev, priv); + if (error) + return error; + + priv->nb.notifier_call =3D asus_ec_keys_notify; + + error =3D blocking_notifier_chain_register(&ec->notify_list, &priv->nb); + if (error) { + input_unregister_handler(&priv->input_handler); + return error; + } + + return 0; +} + +static void asus_ec_keys_remove(struct platform_device *pdev) +{ + struct asus_ec_keys_data *priv =3D platform_get_drvdata(pdev); + struct asusec_core *ec =3D priv->ec; + + blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb); + input_unregister_handler(&priv->input_handler); +} + +static struct platform_driver asus_ec_keys_driver =3D { + .driver.name =3D "asus-transformer-ec-keys", + .probe =3D asus_ec_keys_probe, + .remove =3D asus_ec_keys_remove, +}; +module_platform_driver(asus_ec_keys_driver); + +MODULE_ALIAS("platform:asus-transformer-ec-keys"); +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_DESCRIPTION("ASUS Transformer's multimedia keys driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) (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 E75F044E02C for ; Tue, 21 Jul 2026 09:53:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627602; cv=none; b=uNfVvQEZgpiOar2CeKsHYZnnScNi/7SB22iH/Q6cFNsgk5yawopUsG24WUyC3TsTMzP29cCOinQVgRmNBph0j5GlacOoXoW2sY8BP0H0BcfGvLSNUi6im2bAfv7VaRaRDxhv0yevQfBqd9/06D7fzmEISSzTMlF6ZRCvn3QHDcY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627602; c=relaxed/simple; bh=m4QdZ7W4WUy39laVncWPNH4CPixifxecxugKJLMj/XE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=JwLO5rucP+sFNMts4sBCMO0SSG59u4e7zOZvTph9IMUvL41VMLgnASq6DxT8lQVeaO6pxjJihsBcPod4G28DBA/8r9l66yqYSfjj0h5H0cj/tcND1CT57TW9e1kDmyo5ElEiVtA801DimH2e/KwT7d2tlx0R4Ztoal0dHi5r05I= 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=OmLaKaNP; arc=none smtp.client-ip=209.85.128.51 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="OmLaKaNP" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-4954d29264cso16874715e9.2 for ; Tue, 21 Jul 2026 02:53:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627596; x=1785232396; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=gz4Bferib7A51Cp2ESgO9ZvVsMRzMPGju7EyGtmofdI=; b=OmLaKaNPcGJIv66owusrPQcP4GGxuj1J83Kf9kWElOHZvlRz465xcehacE5InkpKQN EKwhmyWsQ3RjbydvXfqJ/5lhKUEWA7d7lciFvQeyjLKAEJiWPAkuveW2u0P5mhDMeAkT G38UtQ18n5Px2yF4kwx36ttmw2qgYw/a/WVH8Wn7ilp2XxCUPexD11zgy32qRTGwmmTy h6X0MM3W8kuUIdHJYF/TDa2U6CiDuwAD70xey49NK4t10HJeIPxJZGQmEb2a3uf7AYC0 m8EOkw2IyvETiS5Uu2wAHL+CgxXQZ5m7L16iQxmb+xme8C22GQcF76ak8tH6bubhNcoI lqyA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627596; x=1785232396; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=gz4Bferib7A51Cp2ESgO9ZvVsMRzMPGju7EyGtmofdI=; b=dvVcs0PS2jkrlqXuagdeQ2QKuB1Riy9YTHMMx06cUpBBpzAFbVojr8L8FT7qoh3LjR wgjb4cpmWeZwtJPXA0E3B4L32psFF1+0bSmCxD+ScfBk8o2ZasuNyq5VcEI/66HRV+O/ NAfNKybJQyZqF/cz3ET9ngnyby/BzWu49t+zN22E1FLevN47Ov9W8u95fhx7CnyyjqYw C6yfTQOVbjnVyxK2ZLbqnm7Tgus0hB4YmhMSe4NAJcAt9GpxdIZrY+eanmLuJ5WZw5dX fmI5TOkZv6t1mTc8uzcJVhk0LHZI/vMCoyP49+LDvCvwe2eizqFtTubEgpETsC4YMcb5 13Lg== X-Forwarded-Encrypted: i=1; AHgh+RryPxylDDOzk2ayiERMG3zK604qD3ZTOknXCawO6KbVeCLr54MZ2+80FUyPez7EEra0ZMfhmg0h88imCME=@vger.kernel.org X-Gm-Message-State: AOJu0Yyy6RSLsaQdelBCs6uzJVgGcc7T6RQZdeSfIjvTh3nCKTWRQ9ul fls9EcmuZyrxPIPynOGdH5+2GaRdKViPwHqh69HCf5hPpZsAg6QEyY4Z X-Gm-Gg: AfdE7ckKizgsgLK5dNDiEwMmUZSdos1JJxFgWRFxa3MKD22ql7YIl2vkkL/4jNz1HW1 6f2qhtnDP/dWFQjpjb2q20vkA9X4o1mrrN/OFYhcruji7JH+ctN3iUloPPxqeajJnocv8Q9NGiJ gi3XwcW4nw7m/ZqCJUFc2LeNm927T90lp4ZDZYIMTzhYOmrunHFl9WfhJAVuTiW7N/iUpH9GdCa cUBxHOe5Tlf8h+3iUQBtAQwXxr4Gj5K/xhU87pfotvYiX17J5909cgI0aJ7hHDiXKLJq8FAKIES sg37iy/mAt/qJDLdxziW0gs3Vb9wStMGNtCe8kW9gkvlPBqixtZZ+aXUeD87URv5OhJmFD9bTWp bzxGkV4qHyEMjjdsp+SQbW3lC6+pPC3/HlNgiWUV1eesIkxqR+qCMo3onWoClSwb9pHdA31Ohuq 1+nlt8W9sXcvLrmtlQ2jHti7BrxVSvg/m8JQ== X-Received: by 2002:a7b:c344:0:b0:494:282:106c with SMTP id 5b1f17b1804b1-4954a3f5295mr148889305e9.16.1784627595967; Tue, 21 Jul 2026 02:53:15 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:15 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 5/7] leds: Add driver for ASUS Transformer LEDs Date: Tue, 21 Jul 2026 12:52:31 +0300 Message-ID: <20260721095233.420823-6-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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 From: Micha=C5=82 Miros=C5=82aw ASUS Transformer tablets have a green and an amber LED on both the Pad and the Dock. If both LEDs are enabled simultaneously, the emitted light will be yellow. Co-developed-by: Svyatoslav Ryhel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Micha=C5=82 Miros=C5=82aw --- drivers/leds/Kconfig | 11 +++ drivers/leds/Makefile | 1 + drivers/leds/leds-asus-transformer-ec.c | 125 ++++++++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 drivers/leds/leds-asus-transformer-ec.c diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index f4a0a3c8c8705..f637d23400a89 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -120,6 +120,17 @@ config LEDS_OSRAM_AMS_AS3668 To compile this driver as a module, choose M here: the module will be called leds-as3668. =20 +config LEDS_ASUS_TRANSFORMER_EC + tristate "LED Support for Asus Transformer charging LED" + depends on LEDS_CLASS + depends on MFD_ASUS_TRANSFORMER_EC + help + This option enables support for charging indicator on + Asus Transformer's Pad and it's Dock. + + To compile this driver as a module, choose M here: the module + will be called leds-asus-transformer-ec. + config LEDS_AW200XX tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 7db3768912ca5..56304ab90eb31 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_LEDS_ADP5520) +=3D leds-adp5520.o obj-$(CONFIG_LEDS_AN30259A) +=3D leds-an30259a.o obj-$(CONFIG_LEDS_APU) +=3D leds-apu.o obj-$(CONFIG_LEDS_ARIEL) +=3D leds-ariel.o +obj-$(CONFIG_LEDS_ASUS_TRANSFORMER_EC) +=3D leds-asus-transformer-ec.o obj-$(CONFIG_LEDS_OSRAM_AMS_AS3668) +=3D leds-as3668.o obj-$(CONFIG_LEDS_AW200XX) +=3D leds-aw200xx.o obj-$(CONFIG_LEDS_AW2013) +=3D leds-aw2013.o diff --git a/drivers/leds/leds-asus-transformer-ec.c b/drivers/leds/leds-as= us-transformer-ec.c new file mode 100644 index 0000000000000..4421d629911ed --- /dev/null +++ b/drivers/leds/leds-asus-transformer-ec.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +enum { + ASUSEC_LED_AMBER, + ASUSEC_LED_GREEN, + ASUSEC_LED_MAX +}; + +struct asus_ec_led_config { + const char *name; + unsigned int color; + u64 ctrl_bit; +}; + +struct asus_ec_led { + struct asus_ec_leds_data *ddata; + struct led_classdev cdev; + u64 ctrl_bit; +}; + +struct asus_ec_leds_data { + const struct asusec_core *ec; + struct asus_ec_led leds[ASUSEC_LED_MAX]; +}; + +static const struct asus_ec_led_config asus_ec_leds[] =3D { + [ASUSEC_LED_AMBER] =3D { + .name =3D "amber", + .color =3D LED_COLOR_ID_AMBER, + .ctrl_bit =3D ASUSEC_CTL_LED_AMBER, + }, + [ASUSEC_LED_GREEN] =3D { + .name =3D "green", + .color =3D LED_COLOR_ID_GREEN, + .ctrl_bit =3D ASUSEC_CTL_LED_GREEN, + }, +}; + +static enum led_brightness asus_ec_led_get_brightness(struct led_classdev = *cdev) +{ + struct asus_ec_led *led =3D container_of(cdev, struct asus_ec_led, cdev); + const struct asusec_core *ec =3D led->ddata->ec; + u64 ctl; + int ret; + + ret =3D asus_dockram_access_ctl(ec->dockram, &ctl, 0, 0); + if (ret) + return LED_OFF; + + return ctl & led->ctrl_bit ? LED_ON : LED_OFF; +} + +static int asus_ec_led_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct asus_ec_led *led =3D container_of(cdev, struct asus_ec_led, cdev); + const struct asusec_core *ec =3D led->ddata->ec; + + if (brightness) + return asus_dockram_access_ctl(ec->dockram, NULL, + led->ctrl_bit, led->ctrl_bit); + + return asus_dockram_access_ctl(ec->dockram, NULL, led->ctrl_bit, 0); +} + +static int asus_ec_led_probe(struct platform_device *pdev) +{ + const struct asusec_core *ec =3D dev_get_drvdata(pdev->dev.parent); + struct asus_ec_leds_data *ddata; + struct device *dev =3D &pdev->dev; + int ret; + + ddata =3D devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + platform_set_drvdata(pdev, ddata); + ddata->ec =3D ec; + + for (int i =3D 0; i < ASUSEC_LED_MAX; i++) { + const struct asus_ec_led_config *cfg =3D &asus_ec_leds[i]; + struct asus_ec_led *led =3D &ddata->leds[i]; + + led->cdev.name =3D devm_kasprintf(dev, GFP_KERNEL, "%s::%s", + ddata->ec->name, cfg->name); + if (!led->cdev.name) + return -ENOMEM; + + led->cdev.max_brightness =3D 1; + led->cdev.color =3D cfg->color; + led->cdev.flags =3D LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; + led->cdev.brightness_get =3D asus_ec_led_get_brightness; + led->cdev.brightness_set_blocking =3D asus_ec_led_set_brightness; + + led->ddata =3D ddata; + led->ctrl_bit =3D cfg->ctrl_bit; + + ret =3D devm_led_classdev_register(dev, &led->cdev); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register %s LED\n", + cfg->name); + } + + return 0; +} + +static struct platform_driver asus_ec_led_driver =3D { + .driver.name =3D "asus-transformer-ec-led", + .probe =3D asus_ec_led_probe, +}; +module_platform_driver(asus_ec_led_driver); + +MODULE_ALIAS("platform:asus-transformer-ec-led"); +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("ASUS Transformer's charging LED driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f51.google.com (mail-wm1-f51.google.com [209.85.128.51]) (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 AA67F453486 for ; Tue, 21 Jul 2026 09:53:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627604; cv=none; b=Y/mfLz/0MZ1ODSXEG6fMRZtC2XtPGieG4MnN9QPvd0aZatthehhs7iYH2DGuduhx69d9BY3JKuUwvps7e5xhrd5HZg2ZxzWVHu5dsAMLUaBknWkOb1OSBVQZsoSVFsffRIoIksGgmL/EnA9wtiM+e7uqAqiwWOZ9PzPDMLgGdDI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627604; c=relaxed/simple; bh=T4arKghtqZlVK0m2SRh/myGtHU9BpEf1IC2BuSp1lJc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ZAaXMX7WeMTg4gSpwviVfx+hp7YjCG5PUfUgYpAXlOjYwbKdG6K0lMLW7VdOVNNvYgj+7blFqvUxXddo6wgZacMwgV/0GfsxHK6nf87eS16873h9wzB22Ru8Zx6vjSYQtNjjCO8tIYzpl2++2SGUXULQVykMNUpAmZkft+HyOwU= 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=I0w3zpmg; arc=none smtp.client-ip=209.85.128.51 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="I0w3zpmg" Received: by mail-wm1-f51.google.com with SMTP id 5b1f17b1804b1-49550ec592cso13915045e9.0 for ; Tue, 21 Jul 2026 02:53:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627598; x=1785232398; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=HL9rMEjziP6FoJIfXIInE1Pq908vHvvtwganSoqJB24=; b=I0w3zpmgJLUJzkjjyNXHQ0LQPRzcnW8Y3tqV0LjzjmwJ5uyDFwEtOTQoz1GcIuRFWw IEpW/1Kq4caKb8AZdBQiDwvqSYteWuQ8g/4iG2XhUnX0DOFcbUD7ooHfA2yTNo5tieYi nDdHZI5Dw9CN1GVk39960fJ8gOQzsU2c1ddA/uY/tf/G9zjnm4CeK/yya7erS3GBiU6l IdHGGrHYUGynwwHcL1IQZGsSrSo27i4R2z1cKLtknGWzMOT1Pu4Ij9wTHTYkaZmhcg/g 6K/qeo8EeezvSdqHYkdf/KHVj3IYHddLePiNPCbXaDgG0z/L+KFatKP5Hlajs2l5NdV/ QZnA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627598; x=1785232398; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=HL9rMEjziP6FoJIfXIInE1Pq908vHvvtwganSoqJB24=; b=mbi6qIVzI2vJ5CLRQv/RkneNAS+2qMGuy6es4qNC/fsrTWwULZEi6lx7yCzyWwUdp6 Dif+O+9E8ag0/C+ClXsi+wgwleicxSG/AEEIjzn6xTrQ5983xze56UAFQ7xWUsiye+1q M1WR/zX8Q4U5NinO9klmj5BYwB3FRXdjGldikkmdRRAl7ETbffYRQlaW7tZxdAPCfj8m eqU44sAahDmgdjPZg0lKLIa2FY3nIbAusF0Zu3CjsSHJIx4Dzwn/CwpYgKAogd6z4dci p3qNT5rzPY0housnZi6G+ZVzNbSJb/CQaIS0MMaVa/NQaVfcS4XZP2pVhy5lLYJTTBo6 i4yA== X-Forwarded-Encrypted: i=1; AHgh+RqmgxuVE4+kcjOc7p75Tf+CBdCFcRoX9198kXJbK+t9A2T0+hFRCwVc55SLtRDe7JqqrZ+IbSoiZcOpYWc=@vger.kernel.org X-Gm-Message-State: AOJu0Yzw0C0CXby0Eb8ZcfUwJg5+k9SEmaiPJb9azH+7PWG5mNTc/eWY n/thXHit9ytCgKylo4Q5r7oskxHvK6tBdXFumb5jyX9rcF8GqxhsCo3c X-Gm-Gg: AfdE7cmytJ+Zz4lBp8A6wRzFKL6Di4cKZ3nEm+UTRG9C/PUGzSAV8ygSKIQrlCy/xM1 Z9bvlzTZ2s9KAOBqJYIDMFf9bSjEPlkKEikYTtOORtko0kwWauik3lwx/63QCihsKNCGY+dsT8r CmhJQJG5+H35QduD0dv0Bjz6idhfpGVYmuFlXk7QPHBrCWAif0otR9W+mzh+La9EK+jdXUqC49c cxrIqq7zcBzveNbm7qqKdGiqDErRDOGG/+Q4z4hZSxHCPYpsdKoWKwAcbcpRBSoL4lq2stj9ajq S1JrfCj7FlAL2BM47G6LCNrfFkd/aUK7qv6Sj+/YikpgLdGN8Ol4zQ/PipeYp27KWGTEpW5kWHN qKgYVxW6q9JVCiM55JaiSAmhWwMjEkLyu9Fr1UvG3GGmZrylIsIjX3QCuCerlgpUmZIQxma6J0W Cr60bdLq5dZX0/G3z70C1DNzQ= X-Received: by 2002:a05:600c:4e8b:b0:495:501a:fcf8 with SMTP id 5b1f17b1804b1-495501afdc8mr151432985e9.9.1784627597444; Tue, 21 Jul 2026 02:53:17 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:16 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 6/7] power: supply: Add driver for ASUS Transformer battery Date: Tue, 21 Jul 2026 12:52:32 +0300 Message-ID: <20260721095233.420823-7-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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 From: Micha=C5=82 Miros=C5=82aw Driver implements one battery cell per EC controller and supports reading of battery status for ASUS Transformer's pad and mobile dock. Co-developed-by: Svyatoslav Ryhel Signed-off-by: Svyatoslav Ryhel Signed-off-by: Micha=C5=82 Miros=C5=82aw Reviewed-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + .../supply/asus-transformer-ec-battery.c | 289 ++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 drivers/power/supply/asus-transformer-ec-battery.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f0ede1cecb6a8..24996ee6d90dd 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -122,6 +122,17 @@ config BATTERY_CHAGALL This driver can also be built as a module. If so, the module will be called chagall-battery. =20 +config BATTERY_ASUS_TRANSFORMER_EC + tristate "Asus Transformer's battery driver" + depends on MFD_ASUS_TRANSFORMER_EC + help + Say Y to enable support for battery status access on Tegra based + ASUS Transformer devices. + + This sub-driver supports battery cells found in Asus Transformer + tablets and mobile docks and controlled by a special embedded + controller. + config BATTERY_CPCAP tristate "Motorola CPCAP PMIC battery driver" depends on MFD_CPCAP && IIO diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 31fe4a145929a..05c306817b449 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -22,6 +22,7 @@ obj-$(CONFIG_TEST_POWER) +=3D test_power.o obj-$(CONFIG_BATTERY_88PM860X) +=3D 88pm860x_battery.o obj-$(CONFIG_CHARGER_ADP5061) +=3D adp5061.o obj-$(CONFIG_BATTERY_ACT8945A) +=3D act8945a_charger.o +obj-$(CONFIG_BATTERY_ASUS_TRANSFORMER_EC) +=3D asus-transformer-ec-battery= .o obj-$(CONFIG_BATTERY_AXP20X) +=3D axp20x_battery.o obj-$(CONFIG_CHARGER_AXP20X) +=3D axp20x_ac_power.o obj-$(CONFIG_BATTERY_CHAGALL) +=3D chagall-battery.o diff --git a/drivers/power/supply/asus-transformer-ec-battery.c b/drivers/p= ower/supply/asus-transformer-ec-battery.c new file mode 100644 index 0000000000000..4c0c6d4b09e2c --- /dev/null +++ b/drivers/power/supply/asus-transformer-ec-battery.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUSEC_BATTERY_DATA_FRESH_MSEC 5000 + +#define ASUSEC_BATTERY_DISCHARGING BIT(6) +#define ASUSEC_BATTERY_FULL_CHARGED BIT(5) +#define ASUSEC_BATTERY_NOT_CHARGING BIT(4) + +#define TEMP_CELSIUS_OFFSET 2731 + +struct asus_ec_battery_data { + struct asusec_core *ec; + struct power_supply *battery; + struct power_supply_desc psy_desc; + struct delayed_work poll_work; + struct mutex battery_lock; /* for data refresh */ + unsigned long batt_data_ts; + int last_state; + u8 batt_data[ASUSEC_ENTRY_BUFSIZE]; +}; + +static int asus_ec_battery_refresh(struct asus_ec_battery_data *priv) +{ + struct i2c_client *client =3D priv->ec->dockram; + struct device *dev =3D &client->dev; + int ret =3D 0; + + if (time_before(jiffies, priv->batt_data_ts)) + return ret; + + memset(priv->batt_data, 0, ASUSEC_ENTRY_BUFSIZE); + ret =3D i2c_smbus_read_i2c_block_data(client, ASUSEC_DOCKRAM_BATT_CTL, + ASUSEC_ENTRY_SIZE, priv->batt_data); + if (ret < ASUSEC_ENTRY_SIZE) + return ret < 0 ? ret : -EIO; + + if (priv->batt_data[0] > ASUSEC_ENTRY_SIZE) { + dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n", + ASUSEC_ENTRY_BUFSIZE, priv->batt_data, ret); + return -EPROTO; + } + + priv->batt_data_ts =3D jiffies + + msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC); + + return ret; +} + +static enum power_supply_property asus_ec_battery_properties[] =3D { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, + POWER_SUPPLY_PROP_TIME_TO_FULL_NOW, + POWER_SUPPLY_PROP_PRESENT, +}; + +static const unsigned int asus_ec_battery_prop_offs[] =3D { + [POWER_SUPPLY_PROP_STATUS] =3D 1, + [POWER_SUPPLY_PROP_VOLTAGE_MAX] =3D 3, + [POWER_SUPPLY_PROP_CURRENT_MAX] =3D 5, + [POWER_SUPPLY_PROP_TEMP] =3D 7, + [POWER_SUPPLY_PROP_VOLTAGE_NOW] =3D 9, + [POWER_SUPPLY_PROP_CURRENT_NOW] =3D 11, + [POWER_SUPPLY_PROP_CAPACITY] =3D 13, + [POWER_SUPPLY_PROP_CHARGE_NOW] =3D 15, + [POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW] =3D 17, + [POWER_SUPPLY_PROP_TIME_TO_FULL_NOW] =3D 19, +}; + +static int asus_ec_battery_get_value(struct asus_ec_battery_data *priv, + enum power_supply_property psp) +{ + int ret, offs; + + guard(mutex)(&priv->battery_lock); + + if (psp >=3D ARRAY_SIZE(asus_ec_battery_prop_offs)) + return -EINVAL; + + offs =3D asus_ec_battery_prop_offs[psp]; + if (!offs) + return -EINVAL; + + ret =3D asus_ec_battery_refresh(priv); + if (ret < 0) + return ret; + + if (offs >=3D priv->batt_data[0]) + return -ENODATA; + + return get_unaligned_le16(priv->batt_data + offs); +} + +static int asus_ec_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct asus_ec_battery_data *priv =3D power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval =3D 1; + break; + + default: + ret =3D asus_ec_battery_get_value(priv, psp); + if (ret < 0) + return ret; + + val->intval =3D (s16)ret; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (ret & ASUSEC_BATTERY_FULL_CHARGED) + val->intval =3D POWER_SUPPLY_STATUS_FULL; + else if (ret & ASUSEC_BATTERY_NOT_CHARGING) + val->intval =3D POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (ret & ASUSEC_BATTERY_DISCHARGING) + val->intval =3D POWER_SUPPLY_STATUS_DISCHARGING; + else + val->intval =3D POWER_SUPPLY_STATUS_CHARGING; + break; + + case POWER_SUPPLY_PROP_TEMP: + val->intval -=3D TEMP_CELSIUS_OFFSET; + break; + + case POWER_SUPPLY_PROP_CHARGE_NOW: + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_CURRENT_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval *=3D 1000; + break; + + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: + case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: + val->intval *=3D 60; + break; + + default: + break; + } + + break; + } + + return 0; +} + +static void asus_ec_battery_poll_work(struct work_struct *work) +{ + struct asus_ec_battery_data *priv =3D + container_of(work, struct asus_ec_battery_data, poll_work.work); + int state; + + state =3D asus_ec_battery_get_value(priv, POWER_SUPPLY_PROP_STATUS); + if (state < 0) + goto reschedule; + + if (state & ASUSEC_BATTERY_FULL_CHARGED) + state =3D POWER_SUPPLY_STATUS_FULL; + else if (state & ASUSEC_BATTERY_NOT_CHARGING) + state =3D POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state & ASUSEC_BATTERY_DISCHARGING) + state =3D POWER_SUPPLY_STATUS_DISCHARGING; + else + state =3D POWER_SUPPLY_STATUS_CHARGING; + + if (priv->last_state !=3D state) { + priv->last_state =3D state; + power_supply_changed(priv->battery); + } + +reschedule: + /* continuously send uevent notification */ + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC)); +} + +static const struct power_supply_desc asus_ec_battery_desc =3D { + .name =3D "asus-ec-battery", + .type =3D POWER_SUPPLY_TYPE_BATTERY, + .properties =3D asus_ec_battery_properties, + .num_properties =3D ARRAY_SIZE(asus_ec_battery_properties), + .get_property =3D asus_ec_battery_get_property, + .external_power_changed =3D power_supply_changed, +}; + +static int asus_ec_battery_probe(struct platform_device *pdev) +{ + struct asusec_core *ec =3D dev_get_drvdata(pdev->dev.parent); + struct asus_ec_battery_data *priv; + struct device *dev =3D &pdev->dev; + struct power_supply_config cfg =3D { }; + int ret; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + + mutex_init(&priv->battery_lock); + + priv->ec =3D ec; + priv->batt_data_ts =3D jiffies - 1; + priv->last_state =3D POWER_SUPPLY_STATUS_UNKNOWN; + + cfg.fwnode =3D dev_fwnode(dev->parent); + cfg.drv_data =3D priv; + + memcpy(&priv->psy_desc, &asus_ec_battery_desc, sizeof(priv->psy_desc)); + priv->psy_desc.name =3D devm_kasprintf(dev, GFP_KERNEL, "%s-battery", + priv->ec->name); + if (!priv->psy_desc.name) + return -ENOMEM; + + priv->battery =3D devm_power_supply_register(dev, &priv->psy_desc, &cfg); + if (IS_ERR(priv->battery)) + return dev_err_probe(dev, PTR_ERR(priv->battery), + "Failed to register power supply\n"); + + ret =3D devm_delayed_work_autocancel(dev, &priv->poll_work, + asus_ec_battery_poll_work); + if (ret) + return ret; + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC)); + + return 0; +} + +static int __maybe_unused asus_ec_battery_suspend(struct device *dev) +{ + struct asus_ec_battery_data *priv =3D dev_get_drvdata(dev); + + cancel_delayed_work_sync(&priv->poll_work); + + return 0; +} + +static int __maybe_unused asus_ec_battery_resume(struct device *dev) +{ + struct asus_ec_battery_data *priv =3D dev_get_drvdata(dev); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(ASUSEC_BATTERY_DATA_FRESH_MSEC)); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(asus_ec_battery_pm_ops, + asus_ec_battery_suspend, asus_ec_battery_resume); + +static struct platform_driver asus_ec_battery_driver =3D { + .driver =3D { + .name =3D "asus-transformer-ec-battery", + .pm =3D &asus_ec_battery_pm_ops, + }, + .probe =3D asus_ec_battery_probe, +}; +module_platform_driver(asus_ec_battery_driver); + +MODULE_ALIAS("platform:asus-transformer-ec-battery"); +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("ASUS Transformer's battery driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0 From nobody Sat Jul 25 00:56:31 2026 Received: from mail-wm1-f52.google.com (mail-wm1-f52.google.com [209.85.128.52]) (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 549AE44C67F for ; Tue, 21 Jul 2026 09:53:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627605; cv=none; b=eBMr6IAmXPJPVCJx3rMnr1KvAs2gnURsnYAvYhP9ESz9zl4ub7mBkEPLmHKKb9RHhJHLgss2Tj4USk0pahC4fGYMbIw9XlSPiOJC4G2iDA4Ji2mYINsL7D+oVS3tyYPHUz0Y+zdDCo6jU9pAKuQlwGGYy+o8RyTwob14U+Q7kPs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784627605; c=relaxed/simple; bh=02XPcfBtKKLG5GBMMe/FBuJUPOFEPkwRhl6fhRVoCEw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=dZOI3tHNAbj7jcnW4Wr4+ztSYUwa6KAobWfmYh1MwT/gdvgHysD7CHi50iDQvh8u5vqorNY1+b5cGYSW87YIBD8BbH8/8+IMLO3ScL3tlxbIhMH6tGC3/9QpLoyTAh+k+Vu5frDdNUcbMImu6wKb5uaK+UY+vDE6qxS0+C66jgA= 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=cFN+lyH+; arc=none smtp.client-ip=209.85.128.52 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="cFN+lyH+" Received: by mail-wm1-f52.google.com with SMTP id 5b1f17b1804b1-49555a0e68bso13253435e9.2 for ; Tue, 21 Jul 2026 02:53:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784627599; x=1785232399; darn=vger.kernel.org; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to:content-type; bh=b5EMKQXklGPjLSVJt2boktipJrdZnyMaVeQ/pA6Pdts=; b=cFN+lyH+Ln9tRQJxfgSIll3RLFgyXu2y5Dq+dGmV19bMerk9HjZeVqA11h4hGUMlpr q1rPwXNVitzonSoH4um6xMSoGgNoLVTMLtfv9YeAyBCs8uifLFe02eeX1khH3Qm5VNCy zITnPdnUk16pChLwlBe8OnPygLhkbZFYIm1L07727JvEBcd/AuXzFqY2u65hq6CXxrFe pO7JiELFONCBQOIAWGM56c4oWAsMM2uLm+qcHdePFB9oJsGmcE+HBGrhgXada4HpUDuH HoqecqmsvK9KhJCuZ+oJgZ6VKlGzUoG57xfgZ/JjmJebXcVTYqtoWqqn/XFjEEPLEU/N A5QQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784627599; x=1785232399; h=content-transfer-encoding:content-type:mime-version:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=b5EMKQXklGPjLSVJt2boktipJrdZnyMaVeQ/pA6Pdts=; b=dgDUicSpgZ2IeAm5Idd/k6API25Y9K6pjOMA5pqsfF028fzvPIcuEqTGldwigCvyvE +eNs4iFwRs83nBOmWUY7MDsIZqkbM4VwHmUkwPbB0UGGtOFOficyWBq89/VHuGWmqOu4 8AWmzy/BiMfESugt0lwctSVDM4CBCeHTpHmxv+0CZjylnjuwRzVftYP4NcLjiglJvtwR nXthvrubARkHiYNEdG/mB6bwfir2u7ufjXq9GtUNY5LD1IV344YgOLbMJlGJaAU5j8lf FHNIzlajdzC76k+ehy5YN1ZsRHzgciiJsZOLsfgwP7DjhWP0F2CfEao5nuY+zN9Ez0Qe 5Sig== X-Forwarded-Encrypted: i=1; AHgh+RpPjfCNzbi0d3/5UDDcAUHNvazvag54gUgedYjx87X5MRZGesE8oiWY5Pgth8njpd7kDzMfOqfHCtae45c=@vger.kernel.org X-Gm-Message-State: AOJu0Yz0tpW918AOdK4to/98/jW4lhGlenGLN+uY/JdrAC0C/AllJkFQ RnPMtbSTxNb3+MHm+Rbh3vI1+HL3bVlho8hb187hvEsUFz2+3/Fsa7bA X-Gm-Gg: AfdE7cnwKghT8m+UygWKh09LF861zN5YQqYBt+aSq73RglEI/W+bfjxG+m1J2Ymyrzg zKap21Ef80fIGwC/Cub8oIDcTnCgRzjXNCqnd+dIX4lO7OpoWb82+tV2lnWi0JOwlFya9qRX4lZ 67lzJE39tt8dGwF7YLG3kUYIzdfuTE0KtJVyFhSjnhyVfDk4EGODQEM5GL+eliENBf88QpXj+nW Kwv5lJMjyPB57dbLWxgbEqdM+vTposgAeGdwKX9wDrsh7l9AmMJVa/vBHY3Z1kkR7TY/1yCodxK v4ZKaKy9XVFcO/BAhp/NUEp1ytCJLuVFf0RTTlt0We0JujdQ36vP7aYdw+h0SVqhLusVs6cpRNu fQRaxILCTiLj2NZh3EfcR9txnS1vRjKSMnrAYunCJs2yfTjZ7CtJehKjxCW7gMYcq784kdkocAv gy2leOIEinru5gN79c17piibE= X-Received: by 2002:a05:600c:3b10:b0:495:4aff:5fce with SMTP id 5b1f17b1804b1-4954aff609amr193705855e9.32.1784627599015; Tue, 21 Jul 2026 02:53:19 -0700 (PDT) Received: from xeon ([188.163.112.51]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49565482c5asm82834285e9.10.2026.07.21.02.53.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 21 Jul 2026 02:53:18 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v10 7/7] power: supply: Add charger driver for Asus Transformers Date: Tue, 21 Jul 2026 12:52:33 +0300 Message-ID: <20260721095233.420823-8-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260721095233.420823-1-clamor95@gmail.com> References: <20260721095233.420823-1-clamor95@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 From: Micha=C5=82 Miros=C5=82aw Add support for charger detection capabilities found in the embedded controller of ASUS Transformer devices. Suggested-by: Maxim Schwalm Suggested-by: Svyatoslav Ryhel Signed-off-by: Micha=C5=82 Miros=C5=82aw Signed-off-by: Svyatoslav Ryhel Reviewed-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + .../supply/asus-transformer-ec-charger.c | 208 ++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 drivers/power/supply/asus-transformer-ec-charger.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 24996ee6d90dd..3011591c9d755 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -508,6 +508,17 @@ config CHARGER_88PM860X help Say Y here to enable charger for Marvell 88PM860x chip. =20 +config CHARGER_ASUS_TRANSFORMER_EC + tristate "Asus Transformer's charger driver" + depends on MFD_ASUS_TRANSFORMER_EC + help + Say Y here to enable support AC plug detection on Asus Transformer + Dock. + + This sub-driver supports charger detection mechanism found in Asus + Transformer tablets and mobile docks and controlled by special + embedded controller. + config CHARGER_PF1550 tristate "NXP PF1550 battery charger driver" depends on MFD_PF1550 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 05c306817b449..9d51fdb76eee8 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_CHARGER_RT9471) +=3D rt9471.o obj-$(CONFIG_CHARGER_RT9756) +=3D rt9756.o obj-$(CONFIG_BATTERY_TWL4030_MADC) +=3D twl4030_madc_battery.o obj-$(CONFIG_CHARGER_88PM860X) +=3D 88pm860x_charger.o +obj-$(CONFIG_CHARGER_ASUS_TRANSFORMER_EC) +=3D asus-transformer-ec-charger= .o obj-$(CONFIG_CHARGER_PF1550) +=3D pf1550-charger.o obj-$(CONFIG_BATTERY_RX51) +=3D rx51_battery.o obj-$(CONFIG_AB8500_BM) +=3D ab8500_bmdata.o ab8500_charger.o ab8500_fg.o= ab8500_btemp.o ab8500_chargalg.o diff --git a/drivers/power/supply/asus-transformer-ec-charger.c b/drivers/p= ower/supply/asus-transformer-ec-charger.c new file mode 100644 index 0000000000000..c7a6bd2ba533f --- /dev/null +++ b/drivers/power/supply/asus-transformer-ec-charger.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +struct asus_ec_charger_data { + struct notifier_block nb; + struct asusec_core *ec; + struct power_supply *psy; + struct power_supply_desc psy_desc; +}; + +static enum power_supply_property asus_ec_charger_properties[] =3D { + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int asus_ec_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct asus_ec_charger_data *priv =3D power_supply_get_drvdata(psy); + enum power_supply_usb_type psu; + int ret; + u64 ctl; + + /* Check if model name is requested first since it needs no hw access */ + if (psp =3D=3D POWER_SUPPLY_PROP_MODEL_NAME) { + val->strval =3D priv->ec->model; + return 0; + } + + ret =3D asus_dockram_access_ctl(priv->ec->dockram, &ctl, 0, 0); + if (ret) + return ret; + + switch (ctl & (ASUSEC_CTL_FULL_POWER_SOURCE | ASUSEC_CTL_DIRECT_POWER_SOU= RCE)) { + case ASUSEC_CTL_FULL_POWER_SOURCE: + psu =3D POWER_SUPPLY_USB_TYPE_CDP; /* DOCK */ + break; + case ASUSEC_CTL_DIRECT_POWER_SOURCE: + psu =3D POWER_SUPPLY_USB_TYPE_SDP; /* USB */ + break; + case 0: + psu =3D POWER_SUPPLY_USB_TYPE_UNKNOWN; /* no power source connected */ + break; + default: + psu =3D POWER_SUPPLY_USB_TYPE_ACA; /* power adapter */ + break; + } + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval =3D psu !=3D POWER_SUPPLY_USB_TYPE_UNKNOWN; + return 0; + + case POWER_SUPPLY_PROP_USB_TYPE: + val->intval =3D psu; + return 0; + + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + if (ctl & ASUSEC_CTL_TEST_DISCHARGE) + val->intval =3D POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE; + else if (ctl & ASUSEC_CTL_USB_CHARGE) + val->intval =3D POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO; + else + val->intval =3D POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; + return 0; + + default: + return -EINVAL; + } +} + +static int asus_ec_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct asus_ec_charger_data *priv =3D power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + switch ((enum power_supply_charge_behaviour)val->intval) { + case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO: + return asus_dockram_access_ctl(priv->ec->dockram, NULL, + ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE, + ASUSEC_CTL_USB_CHARGE); + + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + return asus_dockram_access_ctl(priv->ec->dockram, NULL, + ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE, 0); + + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + return asus_dockram_access_ctl(priv->ec->dockram, NULL, + ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE, + ASUSEC_CTL_TEST_DISCHARGE); + default: + return -EINVAL; + } + + default: + return -EINVAL; + } +} + +static int asus_ec_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: + return true; + default: + return false; + } +} + +static const struct power_supply_desc asus_ec_charger_desc =3D { + .name =3D "asus-ec-charger", + .type =3D POWER_SUPPLY_TYPE_USB, + .charge_behaviours =3D BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE) | + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE), + .usb_types =3D BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_ACA), + .properties =3D asus_ec_charger_properties, + .num_properties =3D ARRAY_SIZE(asus_ec_charger_properties), + .get_property =3D asus_ec_charger_get_property, + .set_property =3D asus_ec_charger_set_property, + .property_is_writeable =3D asus_ec_charger_property_is_writeable, + .no_thermal =3D true, +}; + +static int asus_ec_charger_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct asus_ec_charger_data *priv =3D + container_of(nb, struct asus_ec_charger_data, nb); + + switch (action) { + case ASUSEC_SMI_ACTION(POWER_NOTIFY): + case ASUSEC_SMI_ACTION(ADAPTER_EVENT): + power_supply_changed(priv->psy); + break; + } + + return NOTIFY_DONE; +} + +static int asus_ec_charger_probe(struct platform_device *pdev) +{ + struct asusec_core *ec =3D dev_get_drvdata(pdev->dev.parent); + struct asus_ec_charger_data *priv; + struct device *dev =3D &pdev->dev; + struct power_supply_config cfg =3D { }; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + priv->ec =3D ec; + + cfg.fwnode =3D dev_fwnode(dev->parent); + cfg.drv_data =3D priv; + + memcpy(&priv->psy_desc, &asus_ec_charger_desc, sizeof(priv->psy_desc)); + priv->psy_desc.name =3D devm_kasprintf(dev, GFP_KERNEL, "%s-charger", + priv->ec->name); + if (!priv->psy_desc.name) + return -ENOMEM; + + priv->psy =3D devm_power_supply_register(dev, &priv->psy_desc, &cfg); + if (IS_ERR(priv->psy)) + return dev_err_probe(dev, PTR_ERR(priv->psy), + "Failed to register power supply\n"); + + priv->nb.notifier_call =3D asus_ec_charger_notify; + + return blocking_notifier_chain_register(&ec->notify_list, &priv->nb); +} + +static void asus_ec_charger_remove(struct platform_device *pdev) +{ + struct asus_ec_charger_data *priv =3D platform_get_drvdata(pdev); + struct asusec_core *ec =3D priv->ec; + + blocking_notifier_chain_unregister(&ec->notify_list, &priv->nb); +} + +static struct platform_driver asus_ec_charger_driver =3D { + .driver.name =3D "asus-transformer-ec-charger", + .probe =3D asus_ec_charger_probe, + .remove =3D asus_ec_charger_remove, +}; +module_platform_driver(asus_ec_charger_driver); + +MODULE_ALIAS("platform:asus-transformer-ec-charger"); +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver"); +MODULE_LICENSE("GPL"); --=20 2.53.0