From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ed1-f41.google.com (mail-ed1-f41.google.com [209.85.208.41]) (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 71C8536C5B4 for ; Sat, 2 May 2026 12:41:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725680; cv=none; b=LnYn923sA9rWTFwL8BOgbvczIBoF8o2n5ESATG+UOA02xOfnHN6BwcU8YnNwpr68YRCCBj5R0HC+u/OvVSmv5kWkPyCFZ5xUxJEIoBOhS1U3hdEF9K3SzzQvkuVcXNqS6120RyJVcEvzDm0iJ73juwx6l6VKC4Yik9CD74/rf/I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725680; c=relaxed/simple; bh=or+EJ07SOsHtoyrRm94X5M8kiBqJd626zfaT/m/MjG0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UiKO5xLK5rqckJ/fcdbx9BTCC6ZBD7sLOGqN4199lbOxXe7wvgZbU1awSfYMclIGOgBnVxavuL62kcftHMQ+8jlWOb3nBZeFCI09Fh+KR6YksxGI0+zSf/rc9K/eOF4nvK6iku6g7gXT6C/lPRsyMfzJTRaVItmblqEfzdSctSE= 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=ZHfA0+pL; arc=none smtp.client-ip=209.85.208.41 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="ZHfA0+pL" Received: by mail-ed1-f41.google.com with SMTP id 4fb4d7f45d1cf-6729c6f0ca7so3496331a12.0 for ; Sat, 02 May 2026 05:41:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725676; x=1778330476; 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=CuxBA1Jnwii0AQo0AYAcpS9dZCQZKs+YbA2PPOQhDTM=; b=ZHfA0+pLz+WXqOpHFmZzji/uxrI6Zwgz5zek+JZ0XVlKGVPVvnvraFpWcm5kI6x6AZ /y4SG8RuyNSuK/noEvqzG3i0vgerbOHeLPuBF8Bw+n9n0PyD1uVzPyRbUj9BdG7zgR+l EaR/AvTZl0rnBkII4yiF53q3RzEdNdl59Ro5eI/ef3lSACAlPLvpqZvpe3+NwhnEuLEo jbo3MVbPLRtIa6MJREf9N903J3DtkXf/AFQdmPRLDvvda76KAuMoH5N6RUXKaSFO9qC2 Bvhq0UXaK+k3HTrjm47+Zwum4/RShfON8H85gWXtEqpP1Y3cU1o45zwPx3zoBuSGmge4 51tA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725676; x=1778330476; 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=CuxBA1Jnwii0AQo0AYAcpS9dZCQZKs+YbA2PPOQhDTM=; b=qseacG5SUzw3xLK6vPihgqyFThESa8d6iF5MtzeYqB6GgyXopwRglvtD8K/dbM4L7Y OHTeqGTpaSOwbkjkPPgSTBrUhsjXt/kt8+ff1zAM101MbWgKcY3kHdpyS+HGGQiVs+m4 TAszDtCkhJRRBNNjLelKXak26jkHWoJQc2haZJ2N/Z16WNVNLf9gPPzxiSJg2AYJawju 5ZnZqNkwKsoQVU9dT8QM4qyRkFv2FVVD+g8oBUes9lyaADCHE/GwhqI2GT1zMBPKoH2Y jPx8iSFbZ4TZo9hyTUUxOevaMYMUujV1TrHZnZgHWybuujjxtFQcibXiNfd591DgwNDO A40g== X-Forwarded-Encrypted: i=1; AFNElJ9jVEojZjAgMPih2KhX/uIY/8RUm4dvj8bEMf+zQC+RxLKyAwG2J+JAAHETmzeYX7HtRKEx4QlkinOjVi4=@vger.kernel.org X-Gm-Message-State: AOJu0YypLKG0Gnm2A2v/Q3gvLZ2GmpjygRebj26H+A5SoF1WO80c0IxC vjR1NDKimtRK1GdeC4DLGxgOUPqjEDdPWXnP5PfWu6mOWqhxnIU0N+jq X-Gm-Gg: AeBDiesDjuHRQEzr+u7+t9ffcNlnjbpCy5PkLvlW55gC0RK6WbrLKaUmxE0HmPxT3R5 DCja6BCHly9Sy0MSeAyVsuqHqwSfA+UI9bgE+TRvL269AE7VTiQPEVMuDQeiorXMUMUbwxawobE +wxbhuHc+ItIWoaMwK+SAfzFDUG85tfoSIDSQMLs3PY94gozNxsvyqX3Ra4wCm6ai2htjtGAGPQ m4IRlXqMp00xLFyHvmQqf9hY7RYDzpuJ49JPnZd4KC/nfW75azmWNnABfSilH4lXpRPWzLisFln mdD1ozbCov88h8mUJRAobxY1GbqiJDYG+7URhEzQVl6CgHoCsl+TiE4eTcZNXHJJQjLAWndvg+t GaZbeLLmCZSFuRUHUfaP6GbVTJYYELflt8JL0kFJ3adcw0E73sWLCs5RE9A4SAmQNh3U9kQK3/+ 2N+v0GyQbiBUcTHrbxQ+7np/I= X-Received: by 2002:aa7:d1d2:0:b0:670:8d90:e861 with SMTP id 4fb4d7f45d1cf-67c18ac5421mr955797a12.6.1777725675575; Sat, 02 May 2026 05:41:15 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:15 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v6 1/7] dt-bindings: embedded-controller: document ASUS Transformer EC Date: Sat, 2 May 2026 15:40:49 +0300 Message-ID: <20260502124055.22475-2-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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.51.0 From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ed1-f52.google.com (mail-ed1-f52.google.com [209.85.208.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1849836C5A1 for ; Sat, 2 May 2026 12:41:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725683; cv=none; b=nJTJOumqxB9KjpjTkov5vknPIe55AKagV1//wjTBOunUvV9H/4tKAeA4uJjvh/FeeyOcsvT+Q2dFjMcwKCyKqCYgGs8J6itDPcFGZ5eXX8m8ryw/GX4y9TSzH12GKm4KG/VU8iPtMz4/OhSbFHvsQ/pUrLD5NgL/Hz7Zhsu0Ecc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725683; c=relaxed/simple; bh=ZnicnZUWzpen3ClhFeLRzWYWXil/ZPzlvgvVo5ZGqyk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=h+XzQC4rj5JS+HIs0Iwa7HqIODwYxwOC8DWPcnedl+nvc37fN5WDLBWafIuhB2n38B5KvLfBJ5IfN5noMSqST1ZKjqONrSRk53reJOS7JYz8Qcf0RCDjfyykANT1oSKqYs1xt8rqqc8IT3phX41oFT22Vm2/SdIAJ3cIczJt+LU= 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=CbjpYNdD; arc=none smtp.client-ip=209.85.208.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="CbjpYNdD" Received: by mail-ed1-f52.google.com with SMTP id 4fb4d7f45d1cf-67b6da5a618so4341569a12.2 for ; Sat, 02 May 2026 05:41:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725678; x=1778330478; 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=JyHPuMDjH1MTCmZmsJ8Z1O5HPmUJGXUVzjm/UA2YFH4=; b=CbjpYNdDLslSzffmH5WNhZmS0/G0sr7JxgvbTzQJtw1U2WcQpksJSvdS54URqZwOXZ E52X0miNEa1nLZBHQT5anBf7ajOQTFj3wuKhrNUrSWIuBBcX4oqdfbzxoq0T5xxxry8r 8o8UEWw5n8+eoXm2roK4DyGPhcRZ255KnM3RDxjSb4K+XinyG9Krsf/cAvc2jyC8nIyJ KwPp5e2i2lo9jQugcYZOvIrKAtjGiLcgXzVr3f0jQPHlu7Ibwl1laNZs6NgmXGS+/4qT l8Uqzo2NvG86v5SqYE+DF/9g2yB8jyxpE2i2AlIq7Tkq06IV6iFYUWR4kIzGweqai/AF giKA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725678; x=1778330478; 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=JyHPuMDjH1MTCmZmsJ8Z1O5HPmUJGXUVzjm/UA2YFH4=; b=LIcIFjagr3fucqOuFo+NcEaRiLB9fPlUYCe1Zl/XqUD5KZ1GBrzJC0MQfr9RybCysu tS0DIPk27Q0r/gXGDdxSZkxKnYeksZwjC01z5r+24kCTB0N68KdlQZtfIpG7roAqYNbr ZItElavt1qZ09DcymvayvzljZ4xeOW5h+A1uI+EpiGNFPcMzxSuI72PWZ8S8C5wdT2wV DnzblxjBZ2k+g7982PBzK5E6vtdVzU8OLE+Yf3SwIST87vkFeUoCfD+hRohs7JWiRq5B GpfYo2TvnNUkKK5e3SY5TIyXeZwXkbVTmtyL5dM6WUfybGrZPXwX0z4ECjsMq7mySsUG ToWQ== X-Forwarded-Encrypted: i=1; AFNElJ8t0+qqB+PBq78mK/0u3xlZ1UjrKwVK90oPNOKzIWNbDXxSDKjjDgXdBwAVYluiKyvD3HLyBuS/Zs0tJiw=@vger.kernel.org X-Gm-Message-State: AOJu0YwDlA6Sbj0Bj2aeoW/geWW/tVEB0OBQpoGg5taSOiipK6TdtMm5 OY2ou2Ystg2o6ASdzN2c5iDDAKLzk8MfIC7ErRI8ZTcw1yLMpD7qlp12 X-Gm-Gg: AeBDievUwRRHqzZOkgVBb5aZi4yKcRsmOaUWDxJYBC2N4qv6q88ojB1Wr1EoNgJIdmX g8gEiqiiYCAfVaqCXjL+6HpsTXU4X00lOvrm2rgE0sIrPSx1veXjMclUcA7fB/nAIhxR/ZbKv+C x9NPrRyrBiS90clE9NokP3mszJOVbQsJQT2VdxGHcEVEy0plK9Io8q8aIHACWuWmwkaHfmoabwL ZsTT6wXUgTpLToQGs3dBUn74+RcKc8fiJjNbvh/FkwkBS9JXNpYGZoJdTTh+zRGPV9IrsZGkOGO eCALqF4btOd3wijJtcCuAGKxGIc8IU1I1Etpqi3crwWkq71rUbA6B6QlICW2fIOBRwS+e1npUHy OPwdxxI+g0nPLozRTl++Z53aMsdiyheI3hA7ZRgz2Dz0ZpgecI8RJUUbIh8ZJmoi2t7bFrkHvOg lHDXqpPPzBDUS6yYzdaE8+p40= X-Received: by 2002:a05:6402:a393:b0:674:74bf:8813 with SMTP id 4fb4d7f45d1cf-67c1b0a8d90mr826921a12.21.1777725678208; Sat, 02 May 2026 05:41:18 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:17 -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 v6 2/7] mfd: Add driver for ASUS Transformer embedded controller Date: Sat, 2 May 2026 15:40:50 +0300 Message-ID: <20260502124055.22475-3-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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 | 14 + drivers/mfd/Makefile | 1 + drivers/mfd/asus-transformer-ec.c | 762 ++++++++++++++++++++++++ include/linux/mfd/asus-transformer-ec.h | 162 +++++ 4 files changed, 939 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..5aa4facfd2df 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -137,6 +137,20 @@ 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" + depends on I2C && OF + help + Support ECs found in ASUS Transformer's Pad and Mobile Dock. + + This provides shared glue for functional part drivers: + asus-transformer-ec-kbc, asus-transformer-ec-keys, + leds-asus-transformer-ec, asus-transformer-ec-battery + and asus-transformer-ec-charger. + + 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..75aa7ab99387 --- /dev/null +++ b/drivers/mfd/asus-transformer-ec.c @@ -0,0 +1,762 @@ +// 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_RSP_BUFFER_SIZE 8 + +struct asus_ec_chip_data { + const char *name; + const struct mfd_cell *mfd_devices; + unsigned int num_devices; + bool clr_fmode; /* clear Factory Mode bit in EC control register */ +}; + +struct asus_ec_data { + struct asusec_info info; + struct mutex ecreq_lock; /* prevent simultaneous access */ + struct gpio_desc *ecreq; + struct i2c_client *self; + const struct asus_ec_chip_data *data; + char ec_data[DOCKRAM_ENTRY_BUFSIZE]; + bool logging_disabled; +}; + +struct dockram_ec_data { + struct mutex ctl_lock; /* prevent simultaneous access */ + char ctl_data[DOCKRAM_ENTRY_BUFSIZE]; +}; + +#define to_ec_data(ec) \ + container_of(ec, struct asus_ec_data, info) + +/** + * asus_dockram_read - Read a register from the DockRAM device. + * @client: Handle to the DockRAM device. + * @reg: Register to read. + * @buf: Byte array into which data will be read; must be large enough to + * hold the data returned by the DockRAM. + * + * This executes the DockRAM read based on the SMBus "block read" protocol + * or its emulation. It extracts DOCKRAM_ENTRY_SIZE bytes from the set + * register address. + * + * Returns a negative errno code else zero on success. + */ +int asus_dockram_read(struct i2c_client *client, int reg, char *buf) +{ + struct device *dev =3D &client->dev; + int ret; + + memset(buf, 0, DOCKRAM_ENTRY_BUFSIZE); + ret =3D i2c_smbus_read_i2c_block_data(client, reg, + DOCKRAM_ENTRY_BUFSIZE, buf); + if (ret < 0) + return ret; + + if (buf[0] > DOCKRAM_ENTRY_SIZE) { + dev_err(dev, "bad data len; buffer: %*ph; ret: %d\n", + DOCKRAM_ENTRY_BUFSIZE, buf, ret); + return -EPROTO; + } + + dev_dbg(dev, "got data; buffer: %*ph; ret: %d\n", + DOCKRAM_ENTRY_BUFSIZE, buf, ret); + + return 0; +} +EXPORT_SYMBOL_GPL(asus_dockram_read); + +/** + * asus_dockram_write - Write a byte array to a register of the DockRAM de= vice. + * @client: Handle to the DockRAM device. + * @reg: Register to write to. + * @buf: Byte array to be written (up to DOCKRAM_ENTRY_SIZE bytes). + * + * This executes the DockRAM write based on the SMBus "block write" + * protocol or its emulation. It writes DOCKRAM_ENTRY_SIZE bytes to the + * specified register address. + * + * Returns a negative errno code else zero on success. + */ +int asus_dockram_write(struct i2c_client *client, int reg, const char *buf) +{ + if (buf[0] > DOCKRAM_ENTRY_SIZE) + return -EINVAL; + + dev_dbg(&client->dev, "sending data; buffer: %*ph\n", buf[0] + 1, buf); + + return i2c_smbus_write_i2c_block_data(client, reg, buf[0] + 1, buf); +} +EXPORT_SYMBOL_GPL(asus_dockram_write); + +/** + * 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 *priv =3D i2c_get_clientdata(client); + char *buf =3D priv->ctl_data; + u64 val; + int ret =3D 0; + + guard(mutex)(&priv->ctl_lock); + + ret =3D asus_dockram_read(client, ASUSEC_DOCKRAM_CONTROL, buf); + if (ret < 0) + goto exit; + + if (buf[0] !=3D ASUSEC_CTL_SIZE) { + ret =3D -EPROTO; + goto exit; + } + + 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 asus_dockram_write(client, ASUSEC_DOCKRAM_CONTROL, buf); + } + +exit: + if (ret < 0) + dev_err(&client->dev, "Failed to access control flags: %d\n", + ret); + + return ret; +} +EXPORT_SYMBOL_GPL(asus_dockram_access_ctl); + +static void asus_ec_remove_notifier(struct device *dev, void *res) +{ + struct asusec_info *ec =3D dev_get_drvdata(dev->parent); + struct notifier_block **nb =3D res; + + blocking_notifier_chain_unregister(&ec->notify_list, *nb); +} + +/** + * devm_asus_ec_register_notifier - Managed registration of notifier to an + * ASUS EC blocking notifier chain. + * @pdev: Device requesting the notifier (used for resource management). + * @nb: Notifier block to be registered. + * + * Register a notifier to the ASUS EC blocking notifier chain. The notifier + * will be automatically unregistered when the requesting device is detach= ed. + * + * Return: 0 on success or a negative error code on failure. + */ +int devm_asus_ec_register_notifier(struct platform_device *pdev, + struct notifier_block *nb) +{ + struct asusec_info *ec =3D dev_get_drvdata(pdev->dev.parent); + struct notifier_block **res; + int ret; + + res =3D devres_alloc(asus_ec_remove_notifier, sizeof(*res), GFP_KERNEL); + if (!res) + return -ENOMEM; + + *res =3D nb; + ret =3D blocking_notifier_chain_register(&ec->notify_list, nb); + if (ret) { + devres_free(res); + return ret; + } + + devres_add(&pdev->dev, res); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_asus_ec_register_notifier); + +static int asus_ec_signal_request(const struct asusec_info *ec) +{ + struct asus_ec_data *priv =3D to_ec_data(ec); + + guard(mutex)(&priv->ecreq_lock); + + dev_dbg(&priv->self->dev, "EC request\n"); + + gpiod_set_value_cansleep(priv->ecreq, 1); + msleep(50); + + gpiod_set_value_cansleep(priv->ecreq, 0); + msleep(200); + + return 0; +} + +static int asus_ec_write(struct asus_ec_data *priv, u16 data) +{ + int ret =3D i2c_smbus_write_word_data(priv->self, ASUSEC_WRITE_BUF, data); + + dev_dbg(&priv->self->dev, "EC write: %04x, ret =3D %d\n", data, ret); + + return ret; +} + +static int asus_ec_read(struct asus_ec_data *priv, bool in_irq) +{ + int ret =3D i2c_smbus_read_i2c_block_data(priv->self, ASUSEC_READ_BUF, + sizeof(priv->ec_data), + priv->ec_data); + + dev_dbg(&priv->self->dev, "EC read: %*ph, ret =3D %d%s\n", + sizeof(priv->ec_data), priv->ec_data, + ret, in_irq ? "; in irq" : ""); + + return ret; +} + +/** + * asus_ec_i2c_command - Send a 16-bit command to the ASUS EC. + * @ec: Pointer to the shared ASUS EC structure. + * @data: The 16-bit command (word) to be sent. + * + * Return: 0 on success or a negative error code on failure. + */ +int asus_ec_i2c_command(const struct asusec_info *ec, u16 data) +{ + return asus_ec_write(to_ec_data(ec), data); +} +EXPORT_SYMBOL_GPL(asus_ec_i2c_command); + +static void asus_ec_clear_buffer(struct asus_ec_data *priv) +{ + int retry =3D ASUSEC_RSP_BUFFER_SIZE; + + while (retry--) { + if (asus_ec_read(priv, false) < 0) + continue; + + if (priv->ec_data[1] & ASUSEC_OBF_MASK) + continue; + + break; + } +} + +static int asus_ec_log_info(struct asus_ec_data *priv, unsigned int reg, + const char *name, char **out) +{ + char buf[DOCKRAM_ENTRY_BUFSIZE]; + int ret; + + ret =3D asus_dockram_read(priv->info.dockram, reg, buf); + if (ret < 0) + return ret; + + if (!priv->logging_disabled) + dev_info(&priv->self->dev, "%-14s: %.*s\n", name, + buf[0], buf + 1); + + if (out) + *out =3D kstrndup(buf + 1, buf[0], GFP_KERNEL); + + return 0; +} + +static int asus_ec_reset(struct asus_ec_data *priv) +{ + int retry, ret; + + for (retry =3D 0; retry < 3; retry++) { + ret =3D asus_ec_write(priv, 0); + if (!ret) + return 0; + + msleep(300); + } + + return ret; +} + +static int asus_ec_magic_debug(struct asus_ec_data *priv) +{ + u64 flag; + int ret; + + ret =3D asus_ec_get_ctl(&priv->info, &flag); + if (ret < 0) + return ret; + + flag &=3D ASUSEC_CTL_SUSB_MODE; + dev_info(&priv->self->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 *priv, bool on) +{ + dev_info(&priv->self->dev, "Entering %s mode.\n", on ? "factory" : + "normal"); + + return asus_ec_update_ctl(&priv->info, ASUSEC_CTL_FACTORY_MODE, + on ? ASUSEC_CTL_FACTORY_MODE : 0); +} + +static int asus_ec_detect(struct asus_ec_data *priv) +{ + char *model =3D NULL; + int ret; + + ret =3D asus_ec_reset(priv); + if (ret) + goto err_exit; + + asus_ec_clear_buffer(priv); + + ret =3D asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_MODEL, "model", &model= ); + if (ret) + goto err_exit; + + ret =3D asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_FW, "FW version", NULL= ); + if (ret) + goto err_exit; + + ret =3D asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_CFGFMT, "Config format= ", NULL); + if (ret) + goto err_exit; + + ret =3D asus_ec_log_info(priv, ASUSEC_DOCKRAM_INFO_HW, "HW version", NULL= ); + if (ret) + goto err_exit; + + priv->logging_disabled =3D true; + + ret =3D asus_ec_magic_debug(priv); + if (ret) + goto err_exit; + + priv->info.model =3D model; + priv->info.name =3D priv->data->name; + + if (priv->data->clr_fmode) + asus_ec_set_factory_mode(priv, false); + +err_exit: + if (ret) + dev_err(&priv->self->dev, "failed to access EC: %d\n", ret); + + return ret; +} + +static void asus_ec_handle_smi(struct asus_ec_data *priv, unsigned int cod= e) +{ + dev_dbg(&priv->self->dev, "SMI interrupt: 0x%02x\n", code); + + switch (code) { + case ASUSEC_SMI_HANDSHAKE: + case ASUSEC_SMI_RESET: + asus_ec_detect(priv); + break; + } +} + +static irqreturn_t asus_ec_interrupt(int irq, void *dev_id) +{ + struct asus_ec_data *priv =3D dev_id; + unsigned long notify_action; + int ret; + + ret =3D asus_ec_read(priv, true); + if (ret <=3D 0 || !(priv->ec_data[1] & ASUSEC_OBF_MASK)) + return IRQ_NONE; + + notify_action =3D priv->ec_data[1]; + if (notify_action & ASUSEC_SMI_MASK) { + unsigned int code =3D priv->ec_data[2]; + + asus_ec_handle_smi(priv, code); + + notify_action |=3D code << 8; + dev_dbg(&priv->self->dev, "SMI code: 0x%02x\n", code); + } + + blocking_notifier_call_chain(&priv->info.notify_list, + notify_action, priv->ec_data); + + return IRQ_HANDLED; +} + +static ssize_t dockram_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct i2c_client *client =3D filp->private_data; + unsigned int reg, rsize; + ssize_t n_read =3D 0, val; + loff_t off =3D *ppos; + char *data; + int ret; + + reg =3D off / DOCKRAM_ENTRY_SIZE; + off %=3D DOCKRAM_ENTRY_SIZE; + rsize =3D DOCKRAM_ENTRIES * DOCKRAM_ENTRY_SIZE; + + if (!count) + return 0; + + data =3D kmalloc(DOCKRAM_ENTRY_BUFSIZE, GFP_KERNEL); + + while (reg < DOCKRAM_ENTRIES) { + unsigned int len =3D DOCKRAM_ENTRY_SIZE - off; + + if (len > rsize) + len =3D rsize; + + ret =3D asus_dockram_read(client, reg, data); + if (ret < 0) { + if (!n_read) + n_read =3D ret; + break; + } + + val =3D copy_to_user(buf, data + 1 + off, len); + if (val =3D=3D len) + return -EFAULT; + + *ppos +=3D len; + n_read +=3D len; + + if (len =3D=3D rsize) + break; + + rsize -=3D len; + buf +=3D len; + off =3D 0; + ++reg; + } + + kfree(data); + + return n_read; +} + +static int dockram_write_one(struct i2c_client *client, int reg, + const char __user *buf, size_t count) +{ + struct dockram_ec_data *priv =3D i2c_get_clientdata(client); + int ret; + + if (!count || count > DOCKRAM_ENTRY_SIZE) + return -EINVAL; + if (buf[0] !=3D count - 1) + return -EINVAL; + + guard(mutex)(&priv->ctl_lock); + + priv->ctl_data[0] =3D (u8)count; + memcpy(priv->ctl_data + 1, buf, count); + ret =3D asus_dockram_write(client, reg, priv->ctl_data); + + return ret; +} + +static ssize_t dockram_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct i2c_client *client =3D filp->private_data; + unsigned int reg; + loff_t off =3D *ppos; + int ret; + + if (off % DOCKRAM_ENTRY_SIZE !=3D 0) + return -EINVAL; + + reg =3D off / DOCKRAM_ENTRY_SIZE; + if (reg >=3D DOCKRAM_ENTRIES) + return -EINVAL; + + ret =3D dockram_write_one(client, reg, buf, count); + + return ret < 0 ? ret : count; +} + +static const struct debugfs_short_fops dockram_fops =3D { + .read =3D dockram_read, + .write =3D dockram_write, + .llseek =3D default_llseek, +}; + +static int control_reg_get(void *ec, u64 *val) +{ + return asus_ec_get_ctl(ec, val); +} + +static int control_reg_set(void *ec, u64 val) +{ + return asus_ec_update_ctl(ec, ~0ull, val); +} + +DEFINE_DEBUGFS_ATTRIBUTE(control_reg_fops, control_reg_get, + control_reg_set, "%016llx\n"); + +static int ec_request_set(void *ec, u64 val) +{ + if (val) + asus_ec_signal_request(ec); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(ec_request_fops, NULL, ec_request_set, "%llu\n"); + +static int ec_irq_set(void *ec, u64 val) +{ + struct asus_ec_data *priv =3D to_ec_data(ec); + + if (val) + irq_wake_thread(priv->self->irq, priv); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(ec_irq_fops, NULL, ec_irq_set, "%llu\n"); + +static void asus_ec_debugfs_remove(void *debugfs_root) +{ + debugfs_remove_recursive(debugfs_root); +} + +static void devm_asus_ec_debugfs_init(struct device *dev) +{ + struct asusec_info *ec =3D dev_get_drvdata(dev); + struct asus_ec_data *priv =3D to_ec_data(ec); + struct dentry *debugfs_root, *dockram_dir; + char *name =3D devm_kasprintf(dev, GFP_KERNEL, "asus-ec-%s", + priv->data->name); + + debugfs_root =3D debugfs_create_dir(name, NULL); + dockram_dir =3D debugfs_create_dir("dockram", debugfs_root); + + debugfs_create_file("ec_irq", 0200, debugfs_root, ec, + &ec_irq_fops); + debugfs_create_file("ec_request", 0200, debugfs_root, ec, + &ec_request_fops); + debugfs_create_file("control_reg", 0644, dockram_dir, ec, + &control_reg_fops); + debugfs_create_file("dockram", 0644, dockram_dir, + priv->info.dockram, &dockram_fops); + + devm_add_action_or_reset(dev, asus_ec_debugfs_remove, debugfs_root); +} + +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 *priv; + int ret; + + dockram =3D i2c_new_ancillary_device(parent, "dockram", + parent->addr + 2); + 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); + + priv =3D devm_kzalloc(&dockram->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return ERR_PTR(-ENOMEM); + + i2c_set_clientdata(dockram, priv); + mutex_init(&priv->ctl_lock); + + return dockram; +} + +static int asus_ec_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct asus_ec_data *priv; + 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"); + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->data =3D device_get_match_data(dev); + if (!priv->data) + return -ENODEV; + + i2c_set_clientdata(client, priv); + priv->self =3D client; + + priv->info.dockram =3D devm_asus_dockram_get(dev); + if (IS_ERR(priv->info.dockram)) + return dev_err_probe(dev, PTR_ERR(priv->info.dockram), + "failed to get dockram\n"); + + priv->ecreq =3D devm_gpiod_get(dev, "request", GPIOD_OUT_LOW); + if (IS_ERR(priv->ecreq)) + return dev_err_probe(dev, PTR_ERR(priv->ecreq), + "failed to get request GPIO\n"); + + BLOCKING_INIT_NOTIFIER_HEAD(&priv->info.notify_list); + mutex_init(&priv->ecreq_lock); + + asus_ec_signal_request(&priv->info); + + ret =3D asus_ec_detect(priv); + if (ret) + return dev_err_probe(dev, ret, "failed to detect EC version\n"); + + ret =3D devm_request_threaded_irq(dev, client->irq, NULL, + &asus_ec_interrupt, + IRQF_ONESHOT | IRQF_SHARED, + client->name, priv); + 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; + + if (IS_ENABLED(CONFIG_DEBUG_FS)) + devm_asus_ec_debugfs_init(dev); + + return devm_mfd_add_devices(dev, 0, priv->data->mfd_devices, + priv->data->num_devices, NULL, 0, NULL); +} + +static const struct mfd_cell asus_ec_sl101_dock_mfd_devices[] =3D { + { + .name =3D "asus-transformer-ec-kbc", + }, +}; + +static const struct asus_ec_chip_data asus_ec_sl101_dock_data =3D { + .name =3D "dock", + .mfd_devices =3D asus_ec_sl101_dock_mfd_devices, + .num_devices =3D ARRAY_SIZE(asus_ec_sl101_dock_mfd_devices), + .clr_fmode =3D false, +}; + +static const struct mfd_cell asus_ec_tf101_dock_mfd_devices[] =3D { + { + .name =3D "asus-transformer-ec-battery", + .id =3D 1, + }, { + .name =3D "asus-transformer-ec-charger", + .id =3D 1, + }, { + .name =3D "asus-transformer-ec-led", + .id =3D 1, + }, { + .name =3D "asus-transformer-ec-keys", + }, { + .name =3D "asus-transformer-ec-kbc", + }, +}; + +static const struct asus_ec_chip_data asus_ec_tf101_dock_data =3D { + .name =3D "dock", + .mfd_devices =3D asus_ec_tf101_dock_mfd_devices, + .num_devices =3D ARRAY_SIZE(asus_ec_tf101_dock_mfd_devices), + .clr_fmode =3D false, +}; + +static const struct mfd_cell asus_ec_tf201_pad_mfd_devices[] =3D { + { + .name =3D "asus-transformer-ec-battery", + .id =3D 0, + }, { + .name =3D "asus-transformer-ec-led", + .id =3D 0, + }, +}; + +static const struct asus_ec_chip_data asus_ec_tf201_pad_data =3D { + .name =3D "pad", + .mfd_devices =3D asus_ec_tf201_pad_mfd_devices, + .num_devices =3D ARRAY_SIZE(asus_ec_tf201_pad_mfd_devices), + .clr_fmode =3D true, +}; + +static const struct mfd_cell asus_ec_tf600t_pad_mfd_devices[] =3D { + { + .name =3D "asus-transformer-ec-battery", + .id =3D 0, + }, { + .name =3D "asus-transformer-ec-charger", + .id =3D 0, + }, { + .name =3D "asus-transformer-ec-led", + .id =3D 0, + }, +}; + +static const struct asus_ec_chip_data asus_ec_tf600t_pad_data =3D { + .name =3D "pad", + .mfd_devices =3D asus_ec_tf600t_pad_mfd_devices, + .num_devices =3D ARRAY_SIZE(asus_ec_tf600t_pad_mfd_devices), + .clr_fmode =3D true, +}; + +static const struct of_device_id asus_ec_match[] =3D { + { .compatible =3D "asus,sl101-ec-dock", .data =3D &asus_ec_sl101_dock_dat= a }, + { .compatible =3D "asus,tf101-ec-dock", .data =3D &asus_ec_tf101_dock_dat= a }, + { .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_dat= a }, + { } +}; +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..0a72de40352e --- /dev/null +++ b/include/linux/mfd/asus-transformer-ec.h @@ -0,0 +1,162 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __MFD_ASUS_TRANSFORMER_EC_H +#define __MFD_ASUS_TRANSFORMER_EC_H + +#include +#include +#include + +struct i2c_client; + +struct asusec_info { + const char *model; + const char *name; + struct i2c_client *dockram; + struct workqueue_struct *wq; + struct blocking_notifier_head notify_list; +}; + +#define DOCKRAM_ENTRIES 0x100 +#define DOCKRAM_ENTRY_SIZE 32 +#define DOCKRAM_ENTRY_BUFSIZE (DOCKRAM_ENTRY_SIZE + 1) + +/* interrupt sources */ +#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_POWER_NOTIFY 0x31 /* [un]plugging USB cable */ +#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 /* [un]plugging charger to dock */ +#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 + +/* dockram comm */ +int asus_dockram_read(struct i2c_client *client, int reg, char *buf); +int asus_dockram_write(struct i2c_client *client, int reg, const char *buf= ); +int asus_dockram_access_ctl(struct i2c_client *client, + u64 *out, u64 mask, u64 xor); + +/* EC public API */ + +/** + * cell_to_ec - Request the shared ASUS EC structure via a subdevice's pde= v. + * @pdev: EC subdevice pdev requesting access to the shared ASUS EC struct= ure. + * + * Returns a pointer to the asusec_info structure. + */ +static inline struct asusec_info *cell_to_ec(struct platform_device *pdev) +{ + return dev_get_drvdata(pdev->dev.parent); +} + +/** + * asus_ec_get_ctl - Read from the DockRAM control register. + * @ec: Pointer to the shared ASUS EC structure. + * @out: Pointer to the variable where the register value will be stored. + * + * Performs a control register read and stores the value in @out. + * + * Return: 0 on success, or a negative errno code on failure. + */ +static inline int asus_ec_get_ctl(const struct asusec_info *ec, u64 *out) +{ + return asus_dockram_access_ctl(ec->dockram, out, 0, 0); +} + +/** + * asus_ec_update_ctl - Update the DockRAM control register. + * @ec: Pointer to the shared ASUS EC structure. + * @mask: Bitmask of bits to be cleared. + * @xor: Bitmask of bits to be toggled or set (via XOR). + * + * Performs a read-modify-write update on the control register using + * the provided @mask and @xor values. + * + * Return: 0 on success, or a negative errno code on failure. + */ +static inline int asus_ec_update_ctl(const struct asusec_info *ec, + u64 mask, u64 xor) +{ + return asus_dockram_access_ctl(ec->dockram, NULL, mask, xor); +} + +/** + * asus_ec_set_ctl_bits - Sets bits of the DockRAM control register. + * @ec: Pointer to the shared ASUS EC structure. + * @mask: Bitmask of bits to be set. + * + * Sets bits of the control register using the provided @mask value. + * + * Return: 0 on success, or a negative errno code on failure. + */ +static inline int asus_ec_set_ctl_bits(const struct asusec_info *ec, u64 m= ask) +{ + return asus_dockram_access_ctl(ec->dockram, NULL, mask, mask); +} + +/** + * asus_ec_clear_ctl_bits - Clears bits of the DockRAM control register. + * @ec: Pointer to the shared ASUS EC structure. + * @mask: Bitmask of bits to be cleared. + * + * Clears bits of the control register using the provided @mask value. + * + * Return: 0 on success, or a negative errno code on failure. + */ +static inline int asus_ec_clear_ctl_bits(const struct asusec_info *ec, u64= mask) +{ + return asus_dockram_access_ctl(ec->dockram, NULL, mask, 0); +} + +int asus_ec_i2c_command(const struct asusec_info *ec, u16 data); +int devm_asus_ec_register_notifier(struct platform_device *dev, + struct notifier_block *nb); +#endif /* __MFD_ASUS_TRANSFORMER_EC_H */ --=20 2.51.0 From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ed1-f53.google.com (mail-ed1-f53.google.com [209.85.208.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7F91336CDFC for ; Sat, 2 May 2026 12:41:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.53 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725684; cv=none; b=lYesv7bd4OXRHBU05jgdzOO1O+/P0tzOBoezdV66wkZphcdBK3EIVwpuk9uupHL90vnJiDMA15IJxacM7tIqUa0Sm+z2WRFj3fju4dVLcmvcwreDVBtZmb5WOpGh5/pE/6zcEXCTk2e4aiuQIPnmSprvCqHlnNeR9MA+8iERlMM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725684; c=relaxed/simple; bh=DVTWhFsaR7cdJl7ch6Cdddtoyk90yR7GTXbx4wT9YRo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=EwHqaA+Y0fYC/xJDXBeIByn0qu/d/eTFfIKQpwpWWlypAEpG4g6DsUyPMXXmweEEV9sySCHzb7B/6A+L33eY0x7m6Qpz6b05xwc4ngHojRz2ReSssmexjWgJFB/1QI5hvDUotZw0VhO5Z1VhvSKgpK+QAqFyY0tLYUr+IUucEp8= 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=c3ZYKP2b; arc=none smtp.client-ip=209.85.208.53 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="c3ZYKP2b" Received: by mail-ed1-f53.google.com with SMTP id 4fb4d7f45d1cf-676e62faf2bso4385627a12.1 for ; Sat, 02 May 2026 05:41:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725680; x=1778330480; 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=yTudflxyz4B/xZhcbN0oKqXiQZgSfJMlSBvEdhxrgRw=; b=c3ZYKP2bLMptjXHZJekhJoNSRrLEPGEosdXSBJlXkQZOGQEzLz4FypSpjJUekXFlLk 1+g/HwhH5o3EHXgaBemz2h4qdokHcw9qljGI/QejdUOYZffhiy533Bl2fOECBOyn1REN I0CkZwRXGryXTjOvhInqNcvZApeL62be0UfbHtWu2TXBsHmgoRDLyZYPlBayF5yMIrjM XOURAYTt1jt1QGUPYKoI7XLgb9bXb/ZfOxu3yvAknU3bWkJl3oqgD84gCFdG0yWZSaJk DwckgbLPKoARdVWdXDVLHrokUhUlyAJp/uUn0XrrCUxivppB4mTVzovxOj0NqnAomsNX UPOw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725680; x=1778330480; 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=yTudflxyz4B/xZhcbN0oKqXiQZgSfJMlSBvEdhxrgRw=; b=Xwjne7qcB0VNLIkykLCsqkiqDfv82TB35fW2nhTj977X/0cYxarK+zgznZ1NdTdn+1 TycLn5FWMU1Z4WxVyVPHYeALusDdCrZAr0rDvP9yIDbXG1nK6u1MiD6jdEOoiCh8crLU htlK8H46ZrAtCmvz+IMQl4FD0u0bHUgpjLILbXYVBMZe0jGZHAEnOruSlWpUqHks2sVF 4kH7CYJSJjbp0fpYws6qa6H0bLDthIOl7HMrMl5HcjY7EXQARQlWptfondqlbX8dU6BT moOuwxjj9M6cLq6+7hpPVolxeE6InEqJlpMufPrrjyG9uxBaKT8zRBMrFT61SG/kN8oH 8zOw== X-Forwarded-Encrypted: i=1; AFNElJ/fOUFS6xRYHaBr8DCLnqmM4S5J7I1Eh/o+tZBzOrnLP/BPltHSRwyqdrhEBBqZn0D3jbQmSlLNbRYbuGU=@vger.kernel.org X-Gm-Message-State: AOJu0Yz4qpxgXUKVdbYN6xLa+nc+InCMPtPPF+tWz+cOFJt5Wjpo2i8z 7rKsj39vktCCmeECexLQbiiZPV5D8Ew3S8FIQ4fDOBTugZSZofSDzQ5f X-Gm-Gg: AeBDietnlqlS3mD1XUemLpj54Ed6gfwAgnwxQDrXLtVCvZ927vH8wehWVSIu6SV3AKy yG6SqAlQdxUSMziSU/WH+eBSCE7tQKcSN0J8MzGV+DL3061pZXVNuOLD5xmg2wAnK7nrL59wY3O 3Q47SrSCYbaqepcY1qyzVeo1u7466HD5/rmMf5kM6KmnRB076spJPKzKKrUUEqbCBQfeG4kycfd ACFaRNbpgDWyUTb4lb3cxyaitTQenPC1P4DcRbwqZKqGFR8+aJfZGRjoF8mnZjQi8dq1XQ7ak6E aE1pchlTLY/hKKyHqd9ZsJW3nwHDwb1WbVcM9HQMyf8HqJunJz1b721pbxJSyW1dqPYa8ZDWX3o hSaxrU78S3AoZPg8+lYirfXOsva1B/kq835XmE/VIIFgcOqj6if9cnohS0NDEAl+JCbIyueu4n4 d//yjOzz7+Yi0gZS5LPHfhCIU= X-Received: by 2002:aa7:ce16:0:b0:66e:8ca6:e79f with SMTP id 4fb4d7f45d1cf-67c1aaa6068mr799383a12.13.1777725679600; Sat, 02 May 2026 05:41:19 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:18 -0700 (PDT) From: Svyatoslav Ryhel To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Dmitry Torokhov , Lee Jones , Pavel Machek , Sebastian Reichel , Svyatoslav Ryhel , Ion Agorria , =?UTF-8?q?Micha=C5=82=20Miros=C5=82aw?= Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pm@vger.kernel.org Subject: [PATCH v6 3/7] input: serio: Add driver for ASUS Transformer dock keyboard and touchpad Date: Sat, 2 May 2026 15:40:51 +0300 Message-ID: <20260502124055.22475-4-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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 | 147 ++++++++++++++++++ 3 files changed, 163 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..47fd6e48c989 --- /dev/null +++ b/drivers/input/serio/asus-transformer-ec-kbc.c @@ -0,0 +1,147 @@ +// 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_info *ec; + 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; + + n =3D data[0] - 1; + data +=3D 2; + + /* + * We need to replace these incoming data for keys: + * RIGHT_META Press 0xE0 0x27 -> LEFT_ALT Press 0x11 + * RIGHT_META Release 0xE0 0xF0 0x27 -> LEFT_ALT Release 0xF0 0x11 + * COMPOSE Press 0xE0 0x2F -> RIGHT_META Press 0xE0 0x27 + * COMPOSE Release 0xE0 0xF0 0x2F -> RIGHT_META Release 0xE0 0xF0 0x27 + */ + + if (port_idx =3D=3D 0 && n >=3D 2 && data[0] =3D=3D 0xE0) { + if (n =3D=3D 3 && data[1] =3D=3D 0xF0) { + switch (data[2]) { + case 0x27: + data[0] =3D 0xF0; + data[1] =3D 0x11; + n =3D 2; + break; + case 0x2F: + data[2] =3D 0x27; + break; + } + } else if (n =3D=3D 2) { + switch (data[1]) { + case 0x27: + data[0] =3D 0x11; + n =3D 1; + break; + case 0x2F: + data[1] =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) +{ + const struct asusec_info *ec =3D port->port_data; + + return asus_ec_i2c_command(ec, (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 to_i2c_client(pdev->dev.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->ec; + 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 int asus_ec_kbc_probe(struct platform_device *pdev) +{ + struct asusec_info *ec =3D cell_to_ec(pdev); + struct asus_ec_kbc_data *priv; + int ret; + + priv =3D devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + platform_set_drvdata(pdev, priv); + priv->ec =3D ec; + + ret =3D asus_ec_register_serio(pdev, 0, "Keyboard", 0); + if (ret < 0) + return ret; + + ret =3D asus_ec_register_serio(pdev, 1, "Touchpad", I8042_CMD_AUX_SEND); + if (ret < 0) + return ret; + + priv->nb.notifier_call =3D asus_ec_kbc_notify; + + return devm_asus_ec_register_notifier(pdev, &priv->nb); +} + +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_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_DESCRIPTION("ASUS Transformer's Dock keyboard and touchpad controll= er driver"); +MODULE_LICENSE("GPL"); --=20 2.51.0 From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ed1-f46.google.com (mail-ed1-f46.google.com [209.85.208.46]) (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 D1D4E32A3DA for ; Sat, 2 May 2026 12:41:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725686; cv=none; b=LzyUW7gXdXaH1eLyhzvTJ56gT7VF1AOLv8S/zevl8ZlTYMiVX533e01nDeICgMjw5k/GixzIfTNTL5vSXDb1lDOOdu7HrZAViER75kQ0dFEIFlzUbGqlPPL5spYLDUfAP3qlyajBTITYoT09qFV3EtWhOuMjBLqqU5vl8E0WRc8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725686; c=relaxed/simple; bh=DUXNea3JzD8/jok4vmj+OcDfQgPSuP6ZLQG6LwNH5k0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=lVzY8zDq11bL9iZP2tJ7JxHQzc3noYtg4/F1ed7stIiz3EYmYxIy2/9Ee92NGAoO11cNVQdJwdzCAYliNhDWwazssT2ecC5KYcRqd5W6GjAoWZgaKCfx0/znzXLGRa3S8H5hguCA32bYLswjDky3SOhgmQAJKjeRLX43JV3CPuU= 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=l9M+QjrI; arc=none smtp.client-ip=209.85.208.46 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="l9M+QjrI" Received: by mail-ed1-f46.google.com with SMTP id 4fb4d7f45d1cf-678adefbd26so4822319a12.3 for ; Sat, 02 May 2026 05:41:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725681; x=1778330481; 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=3r7Xgisd76f4mplviy2GnnCzBJKTLySjjh9dLhKu1ZM=; b=l9M+QjrI3UDqwp9ibBSH5D9Br6r/Dt6sDHyA+TxbG9ztPt1z0laegSzKXXK28xsVuT L0HNuh7Jn5LrfoTWvCCLQg/ZWfubEMrgV4EovFmRL9GgDNRjijb951PVvk0LQFF57uAX 1nvaHTF+IOFvuhlVg9NDw+yzA6lXo3eLjIQYvYK54KPVbxC4te2W77nXKxqGBR+aY03r CG0fis/TjT6U+taIr/idKuz7fL+EN+YjynAnR5MvQL1BBNQYARw4nOqmgZTKr4fFZ4H6 hadqDslfgiB/Oa6uQrLmCeOuL7OuSI5pPutn2yBdnd0APC79wGPlX0pGpn71V7XIzdTY 9OMg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725681; x=1778330481; 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=3r7Xgisd76f4mplviy2GnnCzBJKTLySjjh9dLhKu1ZM=; b=Q3GGlPlJsZf5JsYPKvCEw4bdFMGxv8NUsRl7c/cMFezroaBotlmF+PjvUTZhpwpSEQ p6KAl65/SvcG+lMLbHweF18XUpU3X8h+UHMvDiqWvD5Z5OE0d/iEtdUIGioMGRk6LiSo 5TYBCGwAw6PZpHDl0Tj6x7YOo0WX9kqtmej0/hxTAKbuZh3wFbIbRMea9jgVYFJ3gw5m STt5f/5161asKhSPVMW1xbPE9INN6vDi6xpCggbgeoyGIOfWXFB/VakrYRvNTRNAzcqs MbYmtmOm8qG9q0iAbNpdCcSWFUKYL/E+0Bf5J18xc4UqAxqvBcc32YrewQAoxQXstDDG 4Ofw== X-Forwarded-Encrypted: i=1; AFNElJ+n7/2EXLQ/Gs7Ce+ynzGvKCptiuYYwod5du7hMQ2tUh7tL0T2pe9LR6f1McHglZh/R+TVelmgEVdHPWZ4=@vger.kernel.org X-Gm-Message-State: AOJu0YzZksBVQrFZUyowF7gHNIGjNJBOr3ihNXb3royf46zFbUKjBifX xxlHiOLbJeoQ0zWGvR2vuA6xnWjqyU0SkODhi8hun1Af+MWZ3ZVfcMIu X-Gm-Gg: AeBDiet5IE/VlOtHFL2nBECJxTtvTgqIda9Y1/Z6QWhkKg32rtMvZYMWDi4uT1vQqZJ lwfRCnl8iyp/5rnvVPmpejmSkrWc3L3Fbmnywzndf6BJ6L1yaQj/BNHJxSgklNaOShJssYEc9/M 9I9xQlusz71Bx2njAn092FbFluQEHGFpK3Uo6qjKzZ6nBf23+zcq8uQuXBC+Nne9plo+I4PDoie 5PiT4I//E3G9GeahcPh3RHprudD99q1D1epvWTTNSVxpoFiXX+M81yTvr8jV3FFqa7v2RAfsjI/ MwCh7ng5FY9thEBBC6xKMBhoVfLKx0/yyMErwYJps5R0ywIciGTVOBj2z5GpihLINNI0ZJyFXZU 8HcXSw++l9TkSRUUv942xORmrkUfVX3JgiXAM9IcANvCNysky+5LXT1jXaStc3Q9xe5Ide3+VtI Bnh3/qd7fBTl1zSxI2D+gmQhxBdwhdRIr1JQ== X-Received: by 2002:a05:6402:455c:b0:672:7145:21bb with SMTP id 4fb4d7f45d1cf-67c188cf6d5mr917153a12.8.1777725680854; Sat, 02 May 2026 05:41:20 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:20 -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 v6 4/7] input: keyboard: Add driver for ASUS Transformer dock multimedia keys Date: Sat, 2 May 2026 15:40:52 +0300 Message-ID: <20260502124055.22475-5-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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. Since this only modifies codes sent by asus-ec-keys it doesn't affect normal keyboards at all. 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 | 272 ++++++++++++++++++ 3 files changed, 283 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..02516ccb0b12 --- /dev/null +++ b/drivers/input/keyboard/asus-transformer-ec-keys.c @@ -0,0 +1,272 @@ +// 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_info *ec; + struct input_dev *xidev; + bool special_key_pressed; + bool special_key_mode; + unsigned short keymap[ASUSEC_EXT_KEY_CODES * 2]; +}; + +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 inp= ut_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int error; + + handle =3D kzalloc_obj(*handle); + if (!handle) + return -ENOMEM; + + handle->dev =3D dev; + handle->handler =3D handler; + handle->name =3D "asusec-media-handler"; + + 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 struct input_handler asus_ec_input_handler =3D { + .name =3D "asusec-media-handler", + .event =3D asus_ec_input_event, + .connect =3D asus_ec_input_connect, + .disconnect =3D asus_ec_input_disconnect, + .id_table =3D asus_ec_input_ids, +}; + +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 key 1 with special key press= ed */ + 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_d= ata, 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 void asus_ec_input_handler_deregister(void *priv) +{ + input_unregister_handler(&asus_ec_input_handler); +} + +static int asus_ec_keys_probe(struct platform_device *pdev) +{ + struct asusec_info *ec =3D cell_to_ec(pdev); + struct i2c_client *parent =3D to_i2c_client(pdev->dev.parent); + struct asus_ec_keys_data *priv; + int ret; + + priv =3D devm_kzalloc(&pdev->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(&pdev->dev); + if (!priv->xidev) + return -ENOMEM; + + priv->xidev->name =3D devm_kasprintf(&pdev->dev, GFP_KERNEL, + "%s Keyboard Ext", ec->model); + priv->xidev->phys =3D devm_kasprintf(&pdev->dev, GFP_KERNEL, + "i2c-%u-%04x", + i2c_adapter_id(parent->adapter), + parent->addr); + asus_ec_keys_setup_keymap(priv); + + ret =3D input_register_device(priv->xidev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register extension keys: %d\n", + ret); + return ret; + } + + asus_ec_input_handler.private =3D priv; + + ret =3D input_register_handler(&asus_ec_input_handler); + if (ret) + return ret; + + ret =3D devm_add_action_or_reset(&pdev->dev, asus_ec_input_handler_deregi= ster, + priv); + if (ret) + return ret; + + priv->nb.notifier_call =3D asus_ec_keys_notify; + + return devm_asus_ec_register_notifier(pdev, &priv->nb); +} + +static struct platform_driver asus_ec_keys_driver =3D { + .driver.name =3D "asus-transformer-ec-keys", + .probe =3D asus_ec_keys_probe, +}; +module_platform_driver(asus_ec_keys_driver); + +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_DESCRIPTION("ASUS Transformer's multimedia keys driver"); +MODULE_LICENSE("GPL"); --=20 2.51.0 From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ej1-f41.google.com (mail-ej1-f41.google.com [209.85.218.41]) (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 1FDB536C9D2 for ; Sat, 2 May 2026 12:41:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.41 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725686; cv=none; b=LK6X0FCpl4Hc8fBooskThlBAg6uWTjoHF1oaYVeFN49MTBbaufxgVA7K/hxK7YJx48vLoNTtoylF+emi57h6jaNhPdu3NwOm3nylXPF2CsaUTG/BVL297qFxWxHQMYIAdcOk2LRiq2GAdMSVFzfW1feLTRrx8tSgGGgZJ7DZx1E= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725686; c=relaxed/simple; bh=ee4Z8BXlgFycQt2dRA5KJB/GvQDzeoiKpo9CXTIodS4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=NnXzmgjKIMMPvN7//g9tvoRp8mfuBRYB+O868minsn4srGctRUNUF9nmGH3rEu4Dz+KcBXvBo1B+8PN2oEGTmTwT6MgGL8Y/Lwney3DSNLflDCGpLM8s4Aw5cBTeiZ0t6xNxESiFc8zLeZa0N6LJk+h/WawRmx53RzZh5Vu+VEg= 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=ltpIHXlt; arc=none smtp.client-ip=209.85.218.41 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="ltpIHXlt" Received: by mail-ej1-f41.google.com with SMTP id a640c23a62f3a-bb91b426e40so464557066b.0 for ; Sat, 02 May 2026 05:41:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725682; x=1778330482; 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=iQpI9S4zHlj4VcZDUQwUF17T7cur1QEg2Dks5jP/SGE=; b=ltpIHXltkaSi3mYkeGidvCtxhXXjuehCfbVIdTGivPV4m2wEk2y7wY1IUh+m/BA356 cdwdR7Ny3CtasQtOLckm14RyzJ7v8UYsjz2pngVnXFc/kIwLNBe8k2PcEoBqfBozrzRB lqBZVYLPS8RyzGmEQnKFt2EKtFQ4685FRZ0k9VWG81y5X0BA9sIE1+fxoGSAgKTU5tqa rVToxKc7K/Kcn4JSSm8zncXWpnhkjCL5AiJ3G7htyWwebbYSH2t/Pw2NF0doSddDSGVO ISQOb+NgoMPL36MtdRKhke5zPVfxoaRzLlT9aPFp/Ug6vxCPqRKWh4uVFISsYYl1rXl7 /Y9g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725682; x=1778330482; 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=iQpI9S4zHlj4VcZDUQwUF17T7cur1QEg2Dks5jP/SGE=; b=QCCHoCSAvHn4VibPr3DqTtXsB2+nQeil8qW9Y5+pPnCcnBT+XNDa+ySHATWdyqGDjH 3fDw2PmQSKvL8Ggf/MNys718UeDBgGF27PvV1Z70urSxWiKXxaXWeAhnGQLO8sNVNfaO 1ZjBoTpuBPRK8OzjXJErmdJxuxAT1AwoLGVPOnuUEBJ8T7NGGC3Gz8LpqjNOhgVI8Sn+ kDf3NbdNXdvYOYwPmDJfMTDhd5c/TdNRiIkD5Pq95INhe0Yy+H1Bhg4Smv/hGaflRlv5 hgnhUSxyj5/P0soyMIOa+DkJGhB9DpIkpYjwQmUJW6QeXjYbSHtKmemTy6VsSgtyfgrQ QfBA== X-Forwarded-Encrypted: i=1; AFNElJ8TETNXf/ZTw9wLLcQZqPve9vWuSSXfGM+rXQZG6diuug31SUsEEmvaKMfPkyZ3KJtVy5zNCg/97RALfVg=@vger.kernel.org X-Gm-Message-State: AOJu0Yy2m5RXUoPgn5+cusPox5gW8afAl374WjcgF43xLYfB2q+nC4MY R4ZB3WQKGcZPYb04fIipKaC5iGI4mQVSJyivvM+h07Q9l6zt3J3PQ893 X-Gm-Gg: AeBDiev7MlJTwxvZIZUPIcoItLCbGD9VTbHBWzf7Pezuap1Q1PFEr4HypoK9sEB/PeT SyOvg0omJY9yuFxnR2iFNiqSRQcBTxobl9Q3x7dXSztlYwhtTNw1xKKXivclbP6/dpCbkmgHJZ8 JLsTE+Yb/OzC71Cy1mgxMVfv57ornwofbgKTzx2wxfsCykURN1cqfNasF4sy0djx8q7U1U4qVrt SN0i7OP84s02PjTF9y2mHotv+9ieQFSOkhN7L2afArXuXqdVnqMONomct0B2stF1+yz1xJfB+ZV HHsUGWnl7rmfkSew8NV6yUGG+m2NiloAEEbqktAyl2ORefIPoXloigNjSqLhxmXKLHnaZb9c3HM PbmqFQL1suHzxwIdq/AU9cLrQV2aFFnNYq/hqS3t03OSW5omIgOujhuo2e7u/Kvf3dg2eMBMDLD eN+s/6vKFTJcyDEWSxTdgIihg= X-Received: by 2002:a17:906:7956:b0:ba9:d5c9:fdfd with SMTP id a640c23a62f3a-bbffcd82dacmr145705566b.31.1777725682097; Sat, 02 May 2026 05:41:22 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.20 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:21 -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 v6 5/7] leds: Add driver for ASUS Transformer LEDs Date: Sat, 2 May 2026 15:40:53 +0300 Message-ID: <20260502124055.22475-6-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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 | 79 +++++++++++++++++++++++++ 3 files changed, 91 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..3186038e3be7 --- /dev/null +++ b/drivers/leds/leds-asus-transformer-ec.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include + +static void asus_ec_led_set_brightness_amber(struct led_classdev *led, + enum led_brightness brightness) +{ + const struct asusec_info *ec =3D dev_get_drvdata(led->dev->parent); + + if (brightness) + asus_ec_set_ctl_bits(ec, ASUSEC_CTL_LED_AMBER); + else + asus_ec_clear_ctl_bits(ec, ASUSEC_CTL_LED_AMBER); +} + +static void asus_ec_led_set_brightness_green(struct led_classdev *led, + enum led_brightness brightness) +{ + const struct asusec_info *ec =3D dev_get_drvdata(led->dev->parent); + + if (brightness) + asus_ec_set_ctl_bits(ec, ASUSEC_CTL_LED_GREEN); + else + asus_ec_clear_ctl_bits(ec, ASUSEC_CTL_LED_GREEN); +} + +static int asus_ec_led_probe(struct platform_device *pdev) +{ + struct asusec_info *ec =3D cell_to_ec(pdev); + struct device *dev =3D &pdev->dev; + struct led_classdev *amber_led, *green_led; + int ret; + + platform_set_drvdata(pdev, ec); + + amber_led =3D devm_kzalloc(dev, sizeof(*amber_led), GFP_KERNEL); + if (!amber_led) + return -ENOMEM; + + amber_led->name =3D devm_kasprintf(dev, GFP_KERNEL, "%s::amber", ec->name= ); + amber_led->max_brightness =3D 1; + amber_led->flags =3D LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; + amber_led->brightness_set =3D asus_ec_led_set_brightness_amber; + + ret =3D devm_led_classdev_register(dev, amber_led); + if (ret) + return dev_err_probe(dev, ret, "failed to register amber LED\n"); + + green_led =3D devm_kzalloc(dev, sizeof(*green_led), GFP_KERNEL); + if (!green_led) + return -ENOMEM; + + green_led->name =3D devm_kasprintf(dev, GFP_KERNEL, "%s::green", ec->name= ); + green_led->max_brightness =3D 1; + green_led->flags =3D LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN; + green_led->brightness_set =3D asus_ec_led_set_brightness_green; + + ret =3D devm_led_classdev_register(dev, green_led); + if (ret) + return dev_err_probe(dev, ret, "failed to register green LED\n"); + + 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_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("ASUS Transformer's charging LED driver"); +MODULE_LICENSE("GPL"); --=20 2.51.0 From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ed1-f52.google.com (mail-ed1-f52.google.com [209.85.208.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 224A9370D4A for ; Sat, 2 May 2026 12:41:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725688; cv=none; b=lYkGVU79alAFwADXAmY2otBUYJbJpkSgNxCTB/prBPxHYbqjWJvnfFx+ycBaZChG8icJaOxBCoQhhjWVZ+eNk7VnDigYmCXRpF/GN3PdcieMNRH/FrHoKldcvULSoncixy8v+BBhRhBNCWC4cXzz0Q6ppKdfAKJT1uKbGt0cOqY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725688; c=relaxed/simple; bh=qjUtWSb/ekD1JTIDdFUTH9bmn26tZG96YSOuRQz7diM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=oU6917txRdOSAKVjU2E1tfiEf3sFns17cChQybZ8BaTzuP+Uwj0rVRc8kjwkF+8eJB263UnSmUvXFx5ZS9m25uerZzZWGuahI4WAD+j3WXSJ8qsyNOd/tAp04whKAVFTXaJkueFVlOnPljubGHhb3NejnPUXWo7MuqivTclG6jU= 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=SPwxAFeY; arc=none smtp.client-ip=209.85.208.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SPwxAFeY" Received: by mail-ed1-f52.google.com with SMTP id 4fb4d7f45d1cf-67c1e0229acso634633a12.1 for ; Sat, 02 May 2026 05:41:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725683; x=1778330483; 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=GiT2aXYXJy1Lv5use2rLfv9kJDfx3eEpfyjgfLEgiMk=; b=SPwxAFeY8WUgelRB2szBwcA8qaeZkYOLJN0i21jvKVcVtqxwYGTEg2xeP/RiPD8edt GqqK+/Vi46tRrWsEKv1k4++jP+upENyJMaD9CwLjFzZhj5ARSSEHvfAZhZ1EmkskD8Q2 jQGfErpjtUBqgwFXTnkrNi9r0Fpy3fRWh/l6VQFruar/X9t7cCcvOayXSBnQSw8BGw2d cDepZZ5osc/64+AUXt7BL7LZAnjTAiwZ+0vpdwQXlanf1KRyGhWBx40uJtEOAIm9VtTl bQ0PiVMn8aVm+hDke1y7eIIGjnkf9U32fhU3UzHi0GNc9KMfeG+YpWXsSXhR/PRkfo1G vIvQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725683; x=1778330483; 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=GiT2aXYXJy1Lv5use2rLfv9kJDfx3eEpfyjgfLEgiMk=; b=WdwHJsN2PxJ4WpVpYK7cAFFtMJ2XQEiI2nLrB/I7t0Pbw+3N9XF7QjIq/kCErS4KJZ lnb2oahI6ndQSAyWUogsEPJNsK7OQA1N5NgK4dSY56zwaiGuxXyOxa8XZS31uatxsl3a M3hFGTDPCmLnnDt4Q2WMA/NOwN8rADGxIIwvjKDCBV2VdFH3gTC4D/wTRjhJsvD8qzdZ fakTQz8buFe0N6L/M9BnRBld0J/2e0kvxIzzdp4Mr9NG6Lkm5rmJzRxKa8lYO179qHGV Bo/DkD7X7JNfc2RJTSauSAx3Vwshiy+OGUpk0DmvHPuqFUC/Z33DllJYvWAtCYzemtq+ nu9w== X-Forwarded-Encrypted: i=1; AFNElJ8PugJHugWdRKA5CnZKANgEv+axGgEGORteVrCFXmM3AQZC2qmjaXVKzddZNdnTlXLfOrQbO9txId97ufw=@vger.kernel.org X-Gm-Message-State: AOJu0YyFjtIFlgay4TaK+37wYuThW6XbPIdheku5UO22ZvZokG+cJnFZ SY+nvu/vb7nkv9qZBT7HhKj7fTyroGE8C6OSeVzsEA6LlYnLJwVnzXdz X-Gm-Gg: AeBDieu2E86Hyao3TjJ9KJzGDpZi+elXCNSwbkGWJ4o6haLJBBa+sDzSs0ZhjdU86xv 6AyPmYD0DflTNczjwoVYfZSDlN4OCjrZhxNBm1n++ZO7fN/FeG6Cnxbs7e5SVugr7lfY9m8osZ/ xDfoZX3YB5IxnUUIQ8NDLveDgpksYrA0V9bpx3QtKB3wNl0EMwpybBZqDbL5JvUratqwCx91NYf BfKwYH/wtkY/1x/V10KuB7PFjiTc2F51oHYreXsd3qB1gj0VaYzstFgngVeowwomhs9myDmS3Fh yonDz+fcgzaCBNIaPvL7n0BHqNy+iBnDuyKKg+0UGdOHQKHFWYqDAsvOiCQqQevTw6FJhmWyKkc ynYWTGxNcJNokG7v0i/EozlpQvyCiI/+Fz3XWHqz4yYOJbBDT+bAEO/mq8hBx2/+Lp6uOYonA7p QjXAY/mVUQfLOTwhnISOM4Fbg9shyWP7vtsA== X-Received: by 2002:a50:ee13:0:b0:676:98a0:1c8d with SMTP id 4fb4d7f45d1cf-67c1992a839mr816259a12.1.1777725683216; Sat, 02 May 2026 05:41:23 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:22 -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 v6 6/7] power: supply: Add driver for ASUS Transformer battery Date: Sat, 2 May 2026 15:40:54 +0300 Message-ID: <20260502124055.22475-7-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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 | 272 ++++++++++++++++++ 3 files changed, 284 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..aefcd3fed6fe --- /dev/null +++ b/drivers/power/supply/asus-transformer-ec-battery.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASUSEC_BATTERY_DATA_FRESH_MSEC 5000 + +#define ASUSEC_BATTERY_DISCHARGING 0x40 +#define ASUSEC_BATTERY_FULL_CHARGED 0x20 +#define ASUSEC_BATTERY_NOT_CHARGING 0x10 + +#define TEMP_CELSIUS_OFFSET 2731 + +struct asus_ec_battery_data { + const struct asusec_info *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[DOCKRAM_ENTRY_BUFSIZE]; +}; + +static int asus_ec_battery_refresh(struct asus_ec_battery_data *priv) +{ + int ret =3D 0; + + guard(mutex)(&priv->battery_lock); + + if (time_before(jiffies, priv->batt_data_ts)) + return ret; + + ret =3D asus_dockram_read(priv->ec->dockram, ASUSEC_DOCKRAM_BATT_CTL, + priv->batt_data); + if (ret < 0) + return ret; + + 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; + + 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) + return; + + if (state & ASUSEC_BATTERY_FULL_CHARGED) + state =3D POWER_SUPPLY_STATUS_FULL; + 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); + } + + /* 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 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 cell_to_ec(pdev); + 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); + + 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_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_AUTHOR("Svyatoslav Ryhel "); +MODULE_DESCRIPTION("ASUS Transformer's battery driver"); +MODULE_LICENSE("GPL"); --=20 2.51.0 From nobody Sun Jun 14 06:08:24 2026 Received: from mail-ed1-f45.google.com (mail-ed1-f45.google.com [209.85.208.45]) (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 C819236C592 for ; Sat, 2 May 2026 12:41:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725689; cv=none; b=ExnBRlANq8fGzgqF49lo96qFAqXmHwgytpxdULbsNYMIgeFS0yFcSu/t2rDtl9H+3EJsomroJZ4Elb6tKFjUhArZFA8rJJmUxP8DF4dlePQFAVa3RnPYumZ79TYWKWcmAjY9m9XnIGZNDY91Ppqy+p0vEOs0fkLkEDlX8ZAlScQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1777725689; c=relaxed/simple; bh=h+nLD17Gg+/kS/+rUXx+SBZ8mWAy9WNiKFCBpWd0IIY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=j+gOpGbqhHy6gwi6ApfdzO+vfhkKQbr4zCgIBItFepD3LS4Wy5vLX7SofJMXY4cAP8h5N1ceZsiP+wU36FYlgimDpKWd/vCg23wqu+nOYVonpfoLNYRzT6ORgK2ryWW7VGpgE2/O6yKP6l5Kwf8AEOHlGQhII45Q8BzLwANFfAo= 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=Yy3uBtL/; arc=none smtp.client-ip=209.85.208.45 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="Yy3uBtL/" Received: by mail-ed1-f45.google.com with SMTP id 4fb4d7f45d1cf-671dad7cac8so3216552a12.0 for ; Sat, 02 May 2026 05:41:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1777725685; x=1778330485; 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=60vGHWLIBcb/8ge5YV5kbkl5vbOh3bjKu7Qv+xzYRB4=; b=Yy3uBtL/hJDLN27kcMisL3cFVPv13r6e1DyY+feqw4AA4YriZTHlSbI8pS2DAKlIjd YvdyWFmtCKMpeCxsua9IaXUmifHPSChKcJBk0kdbIEVxfMLK2XCToI4XelD6I1hJz+DF ocqt33G5dpUIle6lZO8NqZAP8YW9T4TRLehmTPjpnR073IJzg3Et69kBEdtpcIRoN6sd Cl9TPsngKrR245YR7EA/yJtL+KZq4i2rVWLG9/awvpoxGEZDGTF56jMSFvf+rK6UCwRd BbrTOg2pFE8uxzGTIZrj+xYiqeZEsu9WX4tHe4b09kHalyfGDesvx+EoGnWWMUS42Nbq z5NQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1777725685; x=1778330485; 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=60vGHWLIBcb/8ge5YV5kbkl5vbOh3bjKu7Qv+xzYRB4=; b=Kwx7WKlHp/yykCz4WXy76ymWLOwkiCrZznb2N7K4ZhNMtpZttshaQq41KTxdXTBPcV PuNVlEFMTUa6Sz9rs+US3tsdUHtT3E0EUztW8CoFB9UHCdBS4kuiaDE3TK43eUbZD0k2 5MJAiaA/1sDMK4Dycakw21acjusv3CbW9153rCnXVB5i1+we9c1KhA5fYfUyFHXZs4NI fe7R/qVMKepLfpj3FbX81eVowK2x4G/Z2c002pqWESy6VhjuCikvcoMh178iUpbju9ba EMogXZjisSyHodW5aw3O1EDmgJ3KwbOvMWIC/Hb/C0uJqiWpSBQh4/sPGAa0h0ncRYKO wqsg== X-Forwarded-Encrypted: i=1; AFNElJ/PVzLYiV3BirFNvtTK6Bxtoe5uL+o4MQ5U/13VuFLnvWfNahdk6/J6RbamjjdhLHYRSvxsS5Mt2mo7mDk=@vger.kernel.org X-Gm-Message-State: AOJu0YxRh/X5VIyUlBV53WNWRREFWwGp+KO+riCrybTJWBu5fWC45jdG VnLQuaIe4MvgNubceSzgNM6/cSgNyB4o202oxbZPEbaJ1sMa/pYECsSB X-Gm-Gg: AeBDiesquWnxhLv4fXlhLIGbCeKyiQAnEt5UpUW3vA7Mtncfx93SjHxtJcViej9TyB8 +5MyEEDM+E8L/85OBSQv+7fxfpPtFo/HiOlSDs9823kp9KZXE29nMvZY78KyVh0BwAUy8nzEkPC 6hlAKdzbvmj7nMu19Qc1yr/kCGBYaG2vpdcIjNWHx3Xy8uejVFwVemAIg9W9hGF8ijPJgWb/bml r1N4wh3q4nCFvp6DlMf4EFlw834bYqdxSQQWR5RsQOoIQXAPYjb5FqHyruwOSjmXqURsTZt/FeJ E6yHGtJgfiEp8cd3rONHOxXnWTO1gOMwtR2h69toCP+bqL7jUEZFSI51EFitEQNsdert9bgDuEt X3pVWX7rxcxqDKHfUZvkUnxP7ELWOFfAdgHy/386oY4Xt2uZTtIumLdNw0Ob6/m61REe1yflzvz MEhNKydeu0IukDzf6K3YjQm3Y= X-Received: by 2002:a17:907:25c5:b0:ba6:4e0f:e3a8 with SMTP id a640c23a62f3a-bbff950b8afmr137113066b.12.1777725684751; Sat, 02 May 2026 05:41:24 -0700 (PDT) Received: from xeon ([188.163.112.56]) by smtp.gmail.com with ESMTPSA id 4fb4d7f45d1cf-67b85e281cdsm1649649a12.3.2026.05.02.05.41.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 02 May 2026 05:41:23 -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 v6 7/7] power: supply: Add charger driver for Asus Transformers Date: Sat, 2 May 2026 15:40:55 +0300 Message-ID: <20260502124055.22475-8-clamor95@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260502124055.22475-1-clamor95@gmail.com> References: <20260502124055.22475-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 | 193 ++++++++++++++++++ 3 files changed, 205 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..de01f0bf2fd7 --- /dev/null +++ b/drivers/power/supply/asus-transformer-ec-charger.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include + +struct asus_ec_charger_data { + struct notifier_block nb; + const struct asusec_info *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; + + ret =3D asus_ec_get_ctl(priv->ec, &ctl); + 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; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval =3D priv->ec->model; + 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_ec_update_ctl(priv->ec, + ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE, + ASUSEC_CTL_USB_CHARGE); + + case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE: + return asus_ec_clear_ctl_bits(priv->ec, + ASUSEC_CTL_TEST_DISCHARGE | ASUSEC_CTL_USB_CHARGE); + + case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE: + return asus_ec_update_ctl(priv->ec, + 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 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 cell_to_ec(pdev); + + 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); + + 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 devm_asus_ec_register_notifier(pdev, &priv->nb); +} + +static struct platform_driver asus_ec_charger_driver =3D { + .driver.name =3D "asus-transformer-ec-charger", + .probe =3D asus_ec_charger_probe, +}; +module_platform_driver(asus_ec_charger_driver); + +MODULE_AUTHOR("Micha=C5=82 Miros=C5=82aw "); +MODULE_DESCRIPTION("ASUS Transformer Pad battery charger driver"); +MODULE_LICENSE("GPL"); --=20 2.51.0