From nobody Sun Jun 28 02:49:47 2026 Received: from mail-lj1-f170.google.com (mail-lj1-f170.google.com [209.85.208.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8505130E831 for ; Thu, 25 Jun 2026 08:15:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375361; cv=none; b=Q3GPPoRoPK+I71kCBY6wioL1t3QnFW4BUNsG5GchKkZNDkGfIbqir/U8g5VrtL2kI3srdryC16dhB4UzL7SzhhA7olHBh4TONH0lcyKORhLhGRbHl3cv6mxhLcSChpH1HzuXqHyO64cqM1mo8iXu+CGA8rmmIvTru85Zc8kpngs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375361; c=relaxed/simple; bh=3FsZ5EOWNSqtTNJo5ZjfqWqOVA1HZTXxoLvf10ooP8A=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=X8mFsBoPxOBUKL/v48zauEsKgAh0J8V6yBdHfAL8Ixr57n9RGsdCovIeSRHSSVt0Y9Dl3jWOlVRFDkEx3sxUWoLjFmEQ2bHEQ0+rW3RUisUm5Nehc0VtkdnHRpABNkF+71iZzRU7PrF3jNuHNn5sCfaQmfO8CjXF5n4zlxOrra0= 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=M4X0Eotq; arc=none smtp.client-ip=209.85.208.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="M4X0Eotq" Received: by mail-lj1-f170.google.com with SMTP id 38308e7fff4ca-39972d9a66fso17600471fa.1 for ; Thu, 25 Jun 2026 01:15:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375358; x=1782980158; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=0AB/lGNPBN5uckN4odzlPN3QWXl6kpX4z3NWF2IlVP4=; b=M4X0EotqJ18M8yivbNAFyz8VbH/WXcLy46RK/5+aIMe5oS2C6b2pE8AsgZdaBvC0tJ vYJU1vQmD+NUzrygHOjxQYWmPhqtRgToBhbECUOBVkLAXaLtIiMlI8PhWNjqj6hP21it 8GgygGJSjjLLfyZSFCqp5eWaagSgb/aPBIMQrEelKwgiXRBCPRM82AVeH4if1JTAEWdS w3Ya4KEB1NSnw4ewixI8zYivC6299ia/IbXVPY8QdWJJm61oaHJR2Uu9ocPNXn+bjc/Z fbHVHzFN8KL0iyRXmQ9T+gHI4sRAG20V4t6s8znBZrYX7KB/LP1d8aVaCV4swE72a2Jz d2Iw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375358; x=1782980158; 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; bh=0AB/lGNPBN5uckN4odzlPN3QWXl6kpX4z3NWF2IlVP4=; b=nkk0LiVFSZFmtXnJpJfX394cPjH31QQmIlprMQFcpuIL41B5ur+U/CxQ0+hIL4p+ZF VPfI8BQgbzEiL/WK02zMeBphLbF8Gq6KCpruPseerLbotWxcz8YMmZDz24tJ5bqQ9bx5 0/UjnBkQkj2v+9u/jHHHtWhY+G7Al/vHNGSKx1vtfxbh8PhljfddpBFn/Ly/vvl/BP0S nRAGvvLr+ULIg1fy9Knt04D16jdvPhNLeVNS+S9aGxOhND4FOVxe6rsw9ULLgFL57U+c Z6/rg37MMLDtHePsv66/dffXrE1hSgu3C7JvI0tCGNXqMKApNvyZQCud6pTEDK2LV9qg Heyw== X-Forwarded-Encrypted: i=1; AHgh+RpIQdv9eBHY2dCILWamMQ5VWbe/dMLpolYwq2YNUmJvpDh6F4XlnIq2YmjNXJBOKQRYbldtjIPxoJOWU3I=@vger.kernel.org X-Gm-Message-State: AOJu0YylVKFiXz0Smz+9FIrlG2kfqJGE2FajK9a20nv15oGfI6nkzZki SjAoNR+qkkWf0e3ZcHLkrhu98mXxlgJodl+d2g570Djj3Hl0vlMiKjtg X-Gm-Gg: AfdE7cnKnL2cqhSmcOAn+SXvBVaYTipSqX9Mylc5+q0XOb4g+RsMgSy7ZWOv3R/C8Hv n6us+yBRDPPBg3de4W/WTmOKtwFY+iBr1c7i7RhUm69xYbwTcmjUX8iYxtNYLZn9zVfFJwJaazt /rjev3NeMgCRwp0cZbMACHqD3RVCDbyBMsBBznaZkiastWPAa0KRsAYETUZOPbzajCIJGaOx/tp t0hcF6B+4dhK3yI0NRBJzoVk39L5p+fVW+VVMEht9eHZePlYx81nfTHJeeEZWs+HDOgGpvCoqO9 v6XIaymcxI11jqwV5RiHVQLOBaEb1WSofqqME98a5RNJVQe37XhJJgQ7uHOuc+MQE7W4MklCyq1 KKDED1dWvC2h8HfCF32cOPqCm1IfgiCSN9bwu0lPlgJSGtOi0lwSDxdN1niW1XiLvwe1szLwMmz HzaBie0VyaF9I7ZRffYEIAUiUCE4L2OyOgdw== X-Received: by 2002:a2e:9acb:0:b0:38e:d3ec:4f91 with SMTP id 38308e7fff4ca-39acb55f8d6mr2989861fa.8.1782375357649; Thu, 25 Jun 2026 01:15:57 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.15.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:15:57 -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 v9 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Date: Thu, 25 Jun 2026 11:15:23 +0300 Message-ID: <20260625081529.22447-2-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 000000000000..60b6375864aa --- /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 Sun Jun 28 02:49:47 2026 Received: from mail-lf1-f43.google.com (mail-lf1-f43.google.com [209.85.167.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 5649237FF40 for ; Thu, 25 Jun 2026 08:16:02 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.43 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375364; cv=none; b=R16kEB7eBiSE8DgKK4gctpTz38CRH/YTVRUSKAeEzwHo7EKxhXGSrdfEN/0cfj3+qJOxCMwkLZjgG7by+iHkkq7w0AHUs9+R9kgaU0stigmtw+U28gwO23I2yF62yv4HmzH56HqP05UkgJN5OdPlABheGnIN8oQHaWmW4KOqWPE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375364; c=relaxed/simple; bh=o3tnK/Ps/0fAfdbfa3eFlhTTKBfBkLXr16Aq4ojxr5Y=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OunayEqJXzwIxW+fWBQB1bqA7RNwNdkhQlDUiLebY89anvz7DIkysu7PJa2DtPKJ5QsYYUMYwtR4HPbqW6AIS7MOr/WMhnPV+5On4YTs8y+JI2/UEa06C7LYnauW9x+7KzJaqKmPF0qWcSkiVf/LJ97B6HHRvBJGo+R/Ps2TLi0= 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=ZTd93U4y; arc=none smtp.client-ip=209.85.167.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="ZTd93U4y" Received: by mail-lf1-f43.google.com with SMTP id 2adb3069b0e04-5ad4ee80f65so1834361e87.0 for ; Thu, 25 Jun 2026 01:16:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375361; x=1782980161; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=HneaqNt9HVOlbTlszJLkVExFl0j4GxnRhh/CUnr7+Tc=; b=ZTd93U4yKO8wH1BGDYl9ZkSFOMUUwFJ+C+PgovF/6Avd4K2kDRCfQzZZToErizNoy9 XbbrAyGqFAW3lRwYzjtxLqmQG4hFumwfHtd41TkZ3vR4OFzgCKKvXTaNQS2jQsLu+x97 ddmRbiETBk3bMxlB3/j9NALUNVnB/9oXGx4GGsrHkGpzSucivU4w2+htfZfB9TpMpT2A HqgiuVOtcLK44JY6BIXvpSr+M2IvzWutO/uxG5zn1iGHLwgiohrKkv+pyndrXrLTq8as KjmOOY9Ms1ng2t75n522TMdAxB3i8jLnAczi7/isZc8ickmuaOruD68KBgOGKtsJuTBb n6Uw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375361; x=1782980161; 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; bh=HneaqNt9HVOlbTlszJLkVExFl0j4GxnRhh/CUnr7+Tc=; b=J88Rx2fuIEK5dtLRxXvGSyehhbvfefJReQvCPArdR1uMXWeQh5d5XuATCpkGHMMzWJ Rw3YOgK9BafyJaWLOgJoZBksXCjxJIDlUX5z9724dadGBgM+BhIoooOOg9rp6DoLX4T4 vPWhbKuNmBGwHh5bz6h0CLjp6pKnxdig1CDJt7LmhTtuCeUnop44u4zikvRSd5VI509w ouv2Ld2/JRROIE7QcP+ZJroEaLpg19yjThLEmoVviz16HfZbI3TJQmNlI4lOSyJL0CqT 2Tl3+r40ACE+rjSqSWYSpOOQNy2NEkyTxVm6JSb3joJjxXTprZKTUGifSRgjlbLRPZaz C9FQ== X-Forwarded-Encrypted: i=1; AHgh+RqPCunzM9fIf45qr0Fs+00lGtgDqvw//vEIRCV7L1UwB8NBFyB00YLVe09HRZEWfC8ext6Yy8nnzHEZOhE=@vger.kernel.org X-Gm-Message-State: AOJu0Yxv546INhdf08ilVnRSD6a6Yiuz59fN88YZfLpjR/LC21bavKE5 Q7YcRPh/UYlgxvXf279LYfScu/xQDYZ8AkCmF7sga5+RaNhI/5DcxXJ7 X-Gm-Gg: AfdE7cll+QgQgbCMs3VyrJG/znxfoTLlnqJeLo8+uIKstlECWRzr5cEQ3Xuu6+5VO+i 5+o3+QN3QRdAgiNBvKt6oLxJxHwcQccUqvL/IxfvU+zJkK03l7RvC2NF8M3gONe8F5Rv5HCjbPp nQIdxZo6by06Sv3jHsFdQI9ib4Ykx79H3zSccQF5wTI3GW+i6TXM+TRJ40ZNmrnHSePiVCrAot8 lRB7tOreKPcBoLxyFE4DnZHLqnJ9VN8znU/lWjKGzOoExzImNwtbSdTpBSRDqgwwgbz5ZFVqHJH Efg+EhZELowN2holoXiFQMPkAutB8uXjx4IPlcIsQjKwV9H9i3CnAAU+av+Oya90QnSXmvqbU7L PPkKXEYrKWXSDwtsmnDBmc3hKUb2X4fPIWuggiJ4Xsbm/fZvvXBNwBAwK0ugteUendiHNl2wSbx 9fLFUq99IwatfYkBetyGsgI0Qly1sQPQeCOmHamnNf64tQ X-Received: by 2002:a05:6512:1352:b0:5aa:8824:156f with SMTP id 2adb3069b0e04-5aea1f57454mr442936e87.47.1782375360325; Thu, 25 Jun 2026 01:16:00 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.15.57 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:15:59 -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 v9 2/7] mfd: Add driver for ASUS Transformer embedded controller Date: Thu, 25 Jun 2026 11:15:24 +0300 Message-ID: <20260625081529.22447-3-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 7192c9d1d268..e1c32505b97a 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 e75e8045c28a..fd80088d8a9a 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 000000000000..739c66fdaf22 --- /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 000000000000..1c25c3a18355 --- /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 Sun Jun 28 02:49:47 2026 Received: from mail-lf1-f49.google.com (mail-lf1-f49.google.com [209.85.167.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 01B953093D3 for ; Thu, 25 Jun 2026 08:16:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375368; cv=none; b=hjt+tD2a+PYVWoFy/dNOzoZfS/dP94XzHr6UHFwvOlhpOVOJigqj8bZeJGkAbMjVqoqBi7OJLTestP59tEw/ZidPIVpS0px2OuMHiBjyTIi101GjlLxviOqYiaLS5aOyFZZbTPyFRGtJkiF3uLMqhtykfWnpRGezQGUnrDYQi18= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375368; c=relaxed/simple; bh=iAWv0A6q+CUyWdP3lyvQ+6bZid6jgphfO9Ltcb9vw9M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=dVHAIrlsHebQAWDMzslMYcIfvsVk6dfZmFDfaC1BdYUDeCI3qoWKKfJHoPD9+gUmoOJ2EgEoiifQgJdJ64Y+/dERCVPVEFw5KYBIouvh07zy1SABBZ/qAShg+hjFyd0OJ8Uus9brMqfx72GBQ3agqbAcMpvDnRgLOgZJeEbhb/0= 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=Gp239ZMs; arc=none smtp.client-ip=209.85.167.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="Gp239ZMs" Received: by mail-lf1-f49.google.com with SMTP id 2adb3069b0e04-5aa61e3d3f3so1924812e87.0 for ; Thu, 25 Jun 2026 01:16:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375364; x=1782980164; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=DGo625p3GtXwmedhKgT6604w6JvdgSETy0kRYi4ikK0=; b=Gp239ZMsvQBmwtX5/pWaTtRClpwrGSv7Yt4AkUH1wWrRmjPyJxkzJ6RAHuJ2awwctk yRR8kqnvQZB3QJnkC7YdBa/HXA1dDo1TIKx+ByqB49yizE3nfY1WeAOKNJgawZsVCyb7 sAPZ/Aft4MjVmeXMn5YggjSbgOuu92vowWE2GTC1uPIW7TijsNP/Go7+EiTTCvqlCjsa b/LBTsMw2NHbCfjFOky/jObwK6TBq2ETJhJTq+xmzBo/OWv5lTeW1Cv0fJG8Gxrjp47H pRkU0q9liFK+gXklBiGrHjcHetR1QZYKmfmR8t18ZZUlsAkuG1OkTA10rjw32QhjE/DT nm+A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375364; x=1782980164; 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; bh=DGo625p3GtXwmedhKgT6604w6JvdgSETy0kRYi4ikK0=; b=foSKsIf6bbrz22e7UfVCq+YWoMk0V7JS31JdLd8sOckqlRLpPBKq/idyxv7mD+cG7K a1hp0uses5G4reugc2Ll1Kr33mA9SUqLxEuHFkzbHXDpGDQPTwm529fWgVFb/OyjEukW 8S/jiM34mYt4K2y7AoKzYOWRVSblPwTYW8odgvP0FecuJ8zy1/j4SKSGyKEcR5c0iTT5 0owKYE+0Y7XLF6zBSjdpxGKz/DEbMQtJjT+KPABzSWqX3kz2rJztF0SGCKJyBgvAjTaH NiVE2Ex9XQikWRSa9l+d3W2a+Orv2HhPIqifR0borZYARyRqC3zOdrbyfhDwrVk90ZDh ZJUQ== X-Forwarded-Encrypted: i=1; AHgh+RrECRLeHpl8Ii6/MzWVS+Jd8zuvRrmPsdagor81SifT+AsEkG0KdEC2cifTSu49SaOJuqAHUSLoTGR1+QA=@vger.kernel.org X-Gm-Message-State: AOJu0YzpF3Gfp1s0o+0+zq72kx/J6F+81I80GAmmK3CLT3aq9RdZze4U QapwDXo9Nsrc2rPCHB2xzII1qOa3FRYw5yp6QlkBtx7XmFwortrvoaHd X-Gm-Gg: AfdE7cl5TtjglCv20IcZ9vhN97WNpIBtEAfF+0nBzPOqq9j/mP+QkFwfbzDnxttgTGx Y+9KGOFYYtkSyEzSbBqQ4R5PAo0ioUM0SApoY6xiaiQLaywBI1l4hXz/mmHNiE53z9Ph9pPnNIr QzSoTGWPsQAnD5aU2RSi9jD+nxOWBkdOqFwNkvoqKuOfkpj1kKlTb55r6XRr3zdZJVvsHxibAtj V5rx4f8GrqI0U34RsaVr35V9JqnBePq/CkzWS5fOf943hDee+hl89dcyZ4Tyt6BhoQXTXgO+e76 FeTW278nlYZbHhSzPjwnlY4Wxh4KWOuBbnYsCj0EX6p2kN7Y7avAp/B/pMGOzkTxEgGp1p19/ht PKIhGZgIaBGvave2BXQM2jfM+hx1RjZz3qvIB0zbUkxlqU0JnK41M8UZQhrhUFT8cIkzBjZIXPJ LkcdRL/ODJFEuNQS/nVHu1izgskIcJ6YSdSA== X-Received: by 2002:ac2:4bcd:0:b0:5ae:a240:8d1c with SMTP id 2adb3069b0e04-5aea24094f8mr493794e87.12.1782375364091; Thu, 25 Jun 2026 01:16:04 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.16.00 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:16:02 -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 v9 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Date: Thu, 25 Jun 2026 11:15:25 +0300 Message-ID: <20260625081529.22447-4-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 5f15a6462056..fad29b950309 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 8ab98f4aa28d..fedc37ee102b 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 000000000000..3ddfa9925b2b --- /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 buffer size. + */ + if (data[0] < 2 || data[0] > ASUSEC_ENTRY_BUFSIZE) + return NOTIFY_BAD; + + n =3D data[0] - 1; + 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); + + 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; + + priv->nb.notifier_call =3D asus_ec_kbc_notify; + + 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 Sun Jun 28 02:49:47 2026 Received: from mail-lj1-f178.google.com (mail-lj1-f178.google.com [209.85.208.178]) (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 9F0E93749EF for ; Thu, 25 Jun 2026 08:16:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375369; cv=none; b=MJXw7g6hUmgLBWr7dcPfkxQJro1zPtPQLszxN/jG0iPvMzZZfcoFSbXB6SAu84nxLIV/XN0n8LF2ag+qp3zee+DxGS97GfVZQSJRat1lxuJeQXGKtwD4bPdf/ALs68WWvMLNfO5bGtbM9VfDqCCxvN9igZe6MEzMVAB09mVGN1Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375369; c=relaxed/simple; bh=lBiubhmZmK6lu6oI1oGYOynai1lYb1IaQxoM0UL9fyo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NVXt7kYGKC/kRpg1rLDcvFZvENJl/0ZSO1H+mKrWitbJyhaDypvAj3yT5OHzVqHNsfN+cG31p1pLfsj3qzjXU+yuCwGExdHaY+6umc7cqDjn1qh6CVt0+3HLzVboCxLsHxA1GKG4tHICNrX39eBDrZ3BQaRQMFPnJJuItDqkj2M= 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=E0k+KAQS; arc=none smtp.client-ip=209.85.208.178 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="E0k+KAQS" Received: by mail-lj1-f178.google.com with SMTP id 38308e7fff4ca-39979f72d0cso12457011fa.2 for ; Thu, 25 Jun 2026 01:16:07 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375366; x=1782980166; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=cNsUmdEd+NdwQdWbzfDmNtspAV3ZFxkqG+QiEwpWLAo=; b=E0k+KAQSEQKAvkKclPscvsQ2O08O1w+4pququHAjw24mwxrTkSwFaRDC+kI55JQFt3 lTZPXh+8EMWTfQc2wcifX2oMqSIMOkc/wYx5NCZRLPMyK0wFDBWUC/mrla4q/XTYsmRs mZ/JJ2/ySFt0RU5DL4VzjC/zyR7UVIyCQwdL1CVyhtyupBwMSVMPW4cgbLsYWn4ynT75 vDhqpYaGK5Ugt6kEWdaxsXKYqrGg1p82kfQqs5f+e8jYLbH3GdK8FU8jyZJm6SjJuCTh fRHaDoTSCef5F4xtMwYCTWAeOJlCUIF2lw28KLehiR7NKQKZ+6B2uaN2fAXxEqbxANIB Jr3Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375366; x=1782980166; 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; bh=cNsUmdEd+NdwQdWbzfDmNtspAV3ZFxkqG+QiEwpWLAo=; b=XZbrTbiX6Epo2JpBoWmTooxXCRcK0xkiIK7E1x8ioo5SfVm5+hYViHo7kf9LZVUn8c mV+rIQYeD/ufz8aS5ygsBdAhs6YxXdGMNXCgD/Nq0EGpaWPE0busBfDiqDPzh0aovjkI d6AqaoA/VF9cMlHkYiG5aUMqCsEt4uFK7OJu2qsUvxTgxFfSkYwg/oK6Bp9oZC+vOMpD 0S5QSABENSPGB1MD3e/UWQGnh1k08y53aXiQFwTL7DOR/rIE1GfzklJKPa4ax5rWLiBp 9GGmiMPzpCvv2ZOxJKU2zF1q8KynerJWy/ELfiNzQFKDvhqSFoJ6FBnTe3x7F+Xqqj9O j81g== X-Forwarded-Encrypted: i=1; AHgh+Rr0tlmCUYdzvLgPQUn1YAXcCYP5TgixSjETWu2sFC8fyka9v2McaFc4ZtflZVLRZl9d7LnqNt+5/0471q8=@vger.kernel.org X-Gm-Message-State: AOJu0YxAJ7E4AgIZsBqra3b8/O4c+y4gmQGYGUuOW+62SjtS4IrNKp/O uc+r6zTBMIm0ecK/cf8wuHk9NBq4VtYO6QW5mvEu1GGjq2G7cguyU/sK X-Gm-Gg: AfdE7ckV+M8lJsi7Dxq9KmY1n4fD16ttaePnTH9dHVzSaHdPnfXgvVveyJKhzJO87bQ xafZGvGTQ8dgA6eECddcFwkPW9LdupGRoNsoH1v/seWgCnBDXFVYigK0V5+7TkDdiVC3YFnyA2W PZqlpAbVb+exDt+An4bVPIVHL2/BH7XEfvIKybQZ0TKuQAnmWerRY9p3zVsQYaper/ntBBn9HYI ZForGWocMYIk7+sQ1iZXMJ85GWn2pl2yKoinOWE3m1hF71ZKjCorgk6XV2sVq42S0E0liyDsz/p PPIphSK1c3fQD+0AWmBpoUnfcIlXuxr4ICPjoMjPRsN2+WLhe1hem03vGdVgwbpObuOYuYbSol+ Zrx7Sd3W1aUGqaWCa7y7t71IoPW0fkKU2nKNiDjpqzqE9dLv8yWQpb1ULyhnoRslcA/49ECPB8m C2e3Y7eigu6v45H9jg3ydRdkPC0u6PHs9O8A== X-Received: by 2002:a2e:bea2:0:b0:396:78b8:27df with SMTP id 38308e7fff4ca-39acb695b94mr3764981fa.8.1782375365571; Thu, 25 Jun 2026 01:16:05 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.16.04 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:16:05 -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 v9 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Date: Thu, 25 Jun 2026 11:15:26 +0300 Message-ID: <20260625081529.22447-5-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 | 314 ++++++++++++++++++ 3 files changed, 325 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 9d1019ba0245..913cb4900565 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 60bb7baf802f..0d81096887ad 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 000000000000..53aff3ce7146 --- /dev/null +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c @@ -0,0 +1,314 @@ +// 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) { + input_unregister_device(priv->xidev); + 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_device(priv->xidev); + 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); + input_unregister_device(priv->xidev); +} + +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 Sun Jun 28 02:49:47 2026 Received: from mail-lj1-f174.google.com (mail-lj1-f174.google.com [209.85.208.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4FC53375F9E for ; Thu, 25 Jun 2026 08:16:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375373; cv=none; b=O0kbxzWp36yPHyrHSFnLHSEAQwUL/EKcuLYFP+p9QNaNqBW3qqNqsbUSOr+LYsTbVA0jFBN49V7hdEqJO9CSvhIXSPIRCSR8MjD8TPTCRClLHpJWEU42lqoeY1A23XjUrpUjhJ52e15fTQXKWiJ/ZlYLhYYTSE8792px4H32Lt8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375373; c=relaxed/simple; bh=9oDA10mVcGDGv+sL10nK0ogYvKF0k4VHZamZ70v0O3o=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=ccikv/6X46D5GptqbLZxzlerl+cqu4QrVaL62/1Sg2S5YaQ35nq8Te8eOAcotW0zlng70jRAgC0Am+hBZ3xZ5cVJHLQ+0BNMEZwplFPuG6UvJKJgpH5svngoEdwgIfBdkJ7CL5Lqt1afY8XmHvLQc27J5lvQCcPbHKwdq6AXul8= 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=bu0QUJlF; arc=none smtp.client-ip=209.85.208.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="bu0QUJlF" Received: by mail-lj1-f174.google.com with SMTP id 38308e7fff4ca-39ad400e72dso414121fa.1 for ; Thu, 25 Jun 2026 01:16:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375368; x=1782980168; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bqCosCRy6LaIx8tyejecFxD0SP4hYQhYZ46XfJ3cNnA=; b=bu0QUJlFFE453FbrEBEdk8yFbgSihe5hRyAVayBXdKBddSPFd9b2iZiPjJZWvuke6g b5J820eaBRw/Me8TaVWkA+qDKP3gg2iy2aJDqLnAOQ9swxFxesL/MUi8ZX+CAfA3BU42 XbyYzuwQhYUj4rsOClU/JG+yhmuIq5MdljxNA1VdNPy0/ZSDEhsgBg2vKzidSgCZ20R9 44BAE42Vvf4SuTUPsZgpOGn+FLITLEXWK7ukA9LabuXIqKBG0g7hzTA1eXJ9rhmPcB7A sM7NYZI8gue7Qt5z499I5JyKoNjuDHcGhlUvQPvJbFq43gVwHwnermJYLVLLjFPweg2Z ngiQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375368; x=1782980168; 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; bh=bqCosCRy6LaIx8tyejecFxD0SP4hYQhYZ46XfJ3cNnA=; b=TvLTg7nWOfo3EPqfEl2OK/W2U66S1n82ovS+DGgusESP0ZBBSgDTiKNOxJMp8d8aOU G4k70l8im0rhYTge9K6YVW6K935m7tLEyhvYHKpy8rYIT+WYA+mDFYw0xaq3Vjpf1Wwj 4H9DNjBtrdKWE1UEwxdMKS3Cce+IIAeU8fFG46sgMj8Ggws+8vHw7GtbRDNrarfKnrTS NGs1j4nv/tJ6pqNZGY00DVzUbvy/vQ6YoZ0855J3P0a0CLZekg+4J+niyoUBgt+YXQJ4 eR8ixzUyapgULDFaaJ1FSOrwp/iJekzdqVKrV335/CV0Zn+1puQOzZzDVxlP0UqRnZMN FqXg== X-Forwarded-Encrypted: i=1; AHgh+Rq/lrfmPJoZmj+kiSUSMRVyuLYxGYvmoFiceaoeZcd1JUvk4l+H5NyjgAGmSMhb2Z3gvaNa7D//aFp0J5g=@vger.kernel.org X-Gm-Message-State: AOJu0YzEM5FCm+TkUdmHebO8cG2DmgEO8MAQ07Fu18sOmesPjviP70CG HRupikwFiRPMhb7IQ/sky/JcUWlRMKnLmj153h+xDD6Etr4fRT6XtGKO X-Gm-Gg: AfdE7clTb1NaOXPaS94yk2yj30ASQ9MoXZqJQc0VZZk8GVya3JP9tmxaLnonOHnB3A1 bxwfPxbbAspRxNTQgyexdEMGrOTG7h24+pIu1tL1BrAsdJrc8zoyhJANv6d5vGdkpxODoEP/Aw4 LkJumLEaKu9yx6CXkWjWW5ZBucdtOK4LtfdxqQZm1yGMCzET12uJSbOu3P5VGR+4YbNVw0Jkt27 EmGGjEVqcO+ws7VOuXVve+PvJfyQyQhv99BMiSo2uTEp88n45ilbRQVZtux0RP3AlcXatmZd6iy V8DElrbNdrZMLTlYMJ5xNTWKj4pIhK3VC7OzKXbeUjyP3JyON8Jpq8akND9rXDD4c0bVN/jpWn5 lWeU68zSM0MT+M7CglzzNksywJ13f4AgGWCytEgvlPVnPEr4a/aILJaLV7nwysI53uMi0qeVPNp ye163FqKkBARsa61HTWCd8dIoCq1AWakLY2Q== X-Received: by 2002:a2e:b8d3:0:b0:396:8ac1:53a7 with SMTP id 38308e7fff4ca-39acb57d49emr3541901fa.8.1782375367208; Thu, 25 Jun 2026 01:16:07 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.16.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:16:06 -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 v9 5/7] leds: Add driver for ASUS Transformer LEDs Date: Thu, 25 Jun 2026 11:15:27 +0300 Message-ID: <20260625081529.22447-6-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 f4a0a3c8c870..f637d23400a8 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 8fdb45d5b439..d5395c3f1124 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -16,6 +16,7 @@ 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_AS3668) +=3D leds-as3668.o +obj-$(CONFIG_LEDS_ASUS_TRANSFORMER_EC) +=3D leds-asus-transformer-ec.o obj-$(CONFIG_LEDS_AW200XX) +=3D leds-aw200xx.o obj-$(CONFIG_LEDS_AW2013) +=3D leds-aw2013.o obj-$(CONFIG_LEDS_BCM6328) +=3D leds-bcm6328.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 000000000000..4421d629911e --- /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 Sun Jun 28 02:49:47 2026 Received: from mail-lf1-f50.google.com (mail-lf1-f50.google.com [209.85.167.50]) (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 DAEB33043CE for ; Thu, 25 Jun 2026 08:16:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.167.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375375; cv=none; b=Z6tiHd6FbLEA+wilkKKdlNlxBdJpGGjOt50qUvAgmve9x2GWVRQ1XiJEBOK2t402RphmSMVpnWthuHMYHARLB93+UHQ2RcuvyOx3ljREZoRJJ2ksFbY57kMOsnt+FvjG2y/+iDFtk7rzrCpYAcxYzIUezimMbnbG92uV/BP9HW8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375375; c=relaxed/simple; bh=Nbs9t+FaKCzd9P0/l8cmYwwSWH8WM3J9kyLsEP7GK10=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=qPCOmptaSDPyqIXWPg2n1WXueHNYsKcTjZ8a0RQ1auYP1icCaWMCqypxrKkp/bupJG6efIPD+lJlVbKPSuMveywXqQvu1Ewjb4eASSrifIV7knoKcoBYrehfRSLpB8kj/OIKv3BNrrFOB4VqAD+qrwI0sjV1sakY3xhftDwEM5g= 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=pTDcKTLP; arc=none smtp.client-ip=209.85.167.50 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="pTDcKTLP" Received: by mail-lf1-f50.google.com with SMTP id 2adb3069b0e04-5aa68d7d757so2562567e87.0 for ; Thu, 25 Jun 2026 01:16:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375371; x=1782980171; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=3v77cuV1OU2NxXYiQdpfa0fE5ggihvMJJO5OgBnPK90=; b=pTDcKTLPfxoi7BgMZ1pAU/KGeNI2BG0lamD3GI388ZbBZZJ32YiyPF7eo8Wj+bARVE xSXJ5Yn8xoBPbARXVi1I7iXynZpZC7YzkwcrDq5TYn6nCRyDQlAX/3vC0XQXAMZ/LnnM 3aY2CQ9hHrkFNtQb7SqDQIusHnnzKzxjJNewvtf+9PgBHqXJrvR1k2eRV6WOpVmUTyOl nUIh6SSNxJROf9YI23LxdG0Afr6Y54DeBWtAUG68kUGhLzK2ShifNUObqzKYn313hPXQ j+LEqGs5awk6nN0O9fNTDn9vJKqtjbvgo6dEO5tsLWy9I6EiSLJlHoAsdfo7HbSgbN65 zcYw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375371; x=1782980171; 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; bh=3v77cuV1OU2NxXYiQdpfa0fE5ggihvMJJO5OgBnPK90=; b=RN0o0+BYbt2N22TJEQTmvzbnTeLXmTeRIQc6ZTiozlfsKCsP5aARRMe1TWZ3VJ3pDv KujGP+sCUYMd4hndpC4YN1M60RgqZMnW4u/02zsEjxjF2AKscZSrAT2MEskvAbqv9aso goWsRxkibpd+fe1aFafvVyq2+26n5AxuSl88vcmn9c/byL7X/fjKkvGTtEL7THUnLtpL vVIJXNZYp73LCEBqGkFgYW6cCS+u03vIRR61BsFTcUB/C9JWAbr9hfpkTNUws8ZEzV4l 7VcRLXZZiSLzHqpXcehmBFUj5IYnx/b6tU2UnD8J289xMp4/qr+zxCAE1n+lt5kn7bJN 2rIg== X-Forwarded-Encrypted: i=1; AHgh+RoWtldCqzPmAFejnvqhYvCbZmbgSIp/ZGXXmQc4yx4VyZmMqlc2BzBB0TO+g1iGe1JZidx2SotXILLsOgI=@vger.kernel.org X-Gm-Message-State: AOJu0Yymf+K4r8xtOa72IMusKyOujQjfJX8Iow7vffXtVxi9HetpGXMn Aya56DxwdieQUWquq6H5zDKbDgRW5KwZNKswU+ayGDYIaRXAWjb3sf4G X-Gm-Gg: AfdE7cmTF/tM6dHcQTEPcOMnwGpQs7u0r8jUUQMOOYyCHIAG0q8jBefj55JHBO/et+4 3dtA+Pq2mstL6Ld/TvYJ5L30VolsL7DVp4icSRkkQIKYrL3T2NTLli/7kPWatJafWfaRsg+fV3y /hzvETK7b7nbcbI8vVjuDdfdSrjTvhubihuGjyg8gUxBGhvR4wF7lAljF9MkJgR2tkC1xdiJfbr 18n+1BnZwxBGllF57sqFEtSEOnG7iiO+/sOYb3NazAHaubK7RsNeFUx5+1PZw3hpJ/j4ucLJI3/ zfyhKmhcsl5z1peEgb5bzqscVnW3VwNC1Jok9RT6SBKKvesVsLI4NgfdR6ST5JlWMg1l3pXiPlk SJd1b61flCY2fJQBDfBy4KMoW1mJ7QseMUKBzT9UWILv6vy+C5X0oq0Wg4vWceTpr6ryjqvEHyk Hsf8gha5YNCO3Y4Yk8qJso8m8QAHOiz75QPg== X-Received: by 2002:a05:6512:6713:b0:5ae:a18e:b06b with SMTP id 2adb3069b0e04-5aea1f4cc9fmr452465e87.26.1782375371071; Thu, 25 Jun 2026 01:16:11 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.16.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:16: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 v9 6/7] power: supply: Add driver for ASUS Transformer battery Date: Thu, 25 Jun 2026 11:15:28 +0300 Message-ID: <20260625081529.22447-7-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 83392ed6a8da..1dc3d0b2e021 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 7ee839dca7f3..1313f367715c 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 000000000000..4c0c6d4b09e2 --- /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 Sun Jun 28 02:49:47 2026 Received: from mail-lj1-f171.google.com (mail-lj1-f171.google.com [209.85.208.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C838031578E for ; Thu, 25 Jun 2026 08:16:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375380; cv=none; b=f23SDZMCJ5oR9RvcHX/1vEjJCN+b8wNqiFtnxOi5bxfWKCbSfzmGWi+/r4V9RJrKuYxBAt1YH4/IcfleH7UYeaTdnPKjFiSnKhJxwoUruTyLWYTHn1fsVgCKepFAz/0tg/r8tVZdu3yJ/hTLAxKQBsq0KiaTGWFELM9dtA4cq8s= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1782375380; c=relaxed/simple; bh=BZeR76EIUInu28DOUEmZkLJeduDVI1Rd/wETsjCEWlc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NHC8CNk7GAOAJ3Iozj9Yo6+TWawpEoJhPGkhbUJY0Y5shY9hbXAh2rQvPc9WdxENHv0witDu5baTedJED2aZ+q+yoyaj6u/mpf1POo9juXsqXb4BBI/LZeMJgUcZgne8VDDVwhW6WA5g8EHSN+3LOIFH3VmudHcJrGgrW5BzHYU= 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=M2WNBKDU; arc=none smtp.client-ip=209.85.208.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="M2WNBKDU" Received: by mail-lj1-f171.google.com with SMTP id 38308e7fff4ca-399f5e574b0so22212481fa.1 for ; Thu, 25 Jun 2026 01:16:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782375375; x=1782980175; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=x00WQhcAvK19cn3JrwI03CI0bwW1boDN7WatJHrIxc8=; b=M2WNBKDUsLCtr+4K1/hSbVvxMuh+QCTh18+CCFFog7G0Q/ltTXEMbo2/tATcNtrngK 9esTVZYBRcGbp5ApSghCiKm9BD448JPeM0KLlECOK5Qn8KVmKyz6g8xzWybNnHNX3PLg Wkkp8BwtjW1X5yexjcoTp0B6+A3ps9xyCZO+28gE+nYtA4G+g7HJYLKsSPXWyMRq2+Mp 5ykscvAJ/pNb/fql76Q6uQ8RfOPR1SfixyHfHxYhP0lcba3aGkNeU9cB1kLz7xiuEbii PktHF7aqUitsPs2/yT7q2ROzlQf8/tof0XpuZ6rWHwDylr5t3g9XzwpR5fN8BgZUt2jO 4mMQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782375375; x=1782980175; 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; bh=x00WQhcAvK19cn3JrwI03CI0bwW1boDN7WatJHrIxc8=; b=pChPY/NxeFkGQSDUCwTUoe6PhBwZIS8thvL0WIdkgC04ZwUsvXVMK7UGK1sMAlch8h El3xuhsh4CXmMpN0n2dmU4ouOmUBD4EkiNcx0DJBf+ZaBt2/+ouq1luEM+bIBcx/WE7K eRH04n090IB9CFl2065gI5XBdj4tFwAx9WIwOyncG9vijWA8FyMSFqnwNZrcq4CTf5Kg TglCjAedOT2snV7ay4oBf2vBs9PKLk/xHj2bWCFieTObc6E5Kc4ygqqJ5J1coQHgBn6P kTV7r1JfE/8BtMTQraCuv/9tgFNByVvDOII3h/hRE0xfsoD99GkAeN1oD8IMV1cqSh/+ oSWQ== X-Forwarded-Encrypted: i=1; AHgh+RpSm0bWUVWF/HVWTmPm6M8coRat2k5Y25sFQqktTPa+kJpIDom1uAKjS5vWPdBLgPPUTn9X35dXgTdrwZQ=@vger.kernel.org X-Gm-Message-State: AOJu0Ywh8E3M8PrSKVdkL+aVgbjJvARAbIIx0h/pOIplx1FQccPpq/di HIrxDzVAJAFmoW8LDh8cbIyESjRfqMbJa9opBrXa7hGFyZIZ2KVPX7G6 X-Gm-Gg: AfdE7cmC6VL/eLK88Tgib8wZMVwYbEZP+a8vkVODdk8hl94XngQpmODTCufoZLbN4Fr FawZ7V4sYAaKho8QrH8zjMyBtEOxraF6+f9mk6u3q1ocNIyRBXKNI8xUtLxhF94vFBKy3b9q9e+ vQ/LunhFi14OlRMkHYkL1yA8HZZfbRnzn444cqWwio8lgU1n6NGw4Fe0XO7/VmkBinBLgeXbbW/ mw847Q0Zk4jG8MgT2ECv4SK646k71SaXPMbGE1+t+QU1UGAQcj8Eu2sY9tAzZyWuF9Y7wJTMmUM iU9I7sOjtlQuDMPYSGOLH4UHhyu5mWuMJeKAs8UNsqKhkIA/8IduvkJyr/8xNdvzkDA2bV/FGup q9E39TEaSomVG0xd1HgDDRnIJj0Y8Nlq6Ke3E1Fvs2iwv8yw1VsjYOacvTz6+eIvIexjOZpFcIZ JPP9wyNfhrUO9KeACSTKX+gn0J5zhlDm21Ng== X-Received: by 2002:a05:6512:838e:b0:5aa:6fff:c3e7 with SMTP id 2adb3069b0e04-5aea1f61ad8mr297509e87.34.1782375374864; Thu, 25 Jun 2026 01:16:14 -0700 (PDT) Received: from xeon ([188.163.112.61]) by smtp.gmail.com with ESMTPSA id 38308e7fff4ca-3999afce64dsm39162221fa.14.2026.06.25.01.16.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 25 Jun 2026 01:16: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 v9 7/7] power: supply: Add charger driver for Asus Transformers Date: Thu, 25 Jun 2026 11:15:29 +0300 Message-ID: <20260625081529.22447-8-clamor95@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260625081529.22447-1-clamor95@gmail.com> References: <20260625081529.22447-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 1dc3d0b2e021..ebc6d5c01330 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 1313f367715c..93d17d28081e 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 000000000000..c7a6bd2ba533 --- /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