From nobody Fri Oct 3 20:53:47 2025 Received: from mail-qv1-f51.google.com (mail-qv1-f51.google.com [209.85.219.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 30E4323A58E; Mon, 25 Aug 2025 03:32:41 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092764; cv=none; b=ENhlF/4OxpvlE9M0jslHg5syZuGlXZ4gFgWPuqVTn9pOSErRJZDEDy3YOT8v+lxAHBN1mY69URkfgxw7kHUA1sXS8nesCLevegysev/pvwliEFDPWCplQuNgTTtlQNXkih0WiWRif3kSb/ycXD7HoGY3r5Rw1q+iw7bL8iATeSQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092764; c=relaxed/simple; bh=t55BFdxkrH31UnYC34dwfgiDYqhZHKEdjQ6tBccRueg=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=OP7DZfpD6ax7iHlcLXsE8ELryowHqJonxvR7lohNa33tsMcReoH49rlRL5VyJlMDCWJQ3y8A8HOvvrs+vA3Dj4JmXeQDVlxD6SzDImj9L52RRJRHs1JZkdg0JXlNoZVElRI+GQVVItOWVMhyug9Qh1gNp5EQxwHhtsEbvllulko= 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=SMN2Aa1+; arc=none smtp.client-ip=209.85.219.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="SMN2Aa1+" Received: by mail-qv1-f51.google.com with SMTP id 6a1803df08f44-70dbe6b8142so8554176d6.2; Sun, 24 Aug 2025 20:32:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756092761; x=1756697561; 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=zoFi0fOoSlOYyhxhA49ewaIAMmopl8uXLyivT/7yV50=; b=SMN2Aa1+G+TofK/+cLyZkFNLFSrfFHRCYyT3/5CNhV0irhVoUYzGOiHyqVR+kQUXzW m3lKt44vI6/T5pERVCP7Gbj6CqRfCzf1cYy2Lj3qp24YqjDu7QX6a3XKFXilr9yqdNIT 9/uG7/Xs8yWwIoyUeUcceiZpUsm2JhMEc+5dbjlnZycaaqKIKGGRRm7SzT5a9tPlMgTV PzUYuCR7WWmkjzkCpg7Y01mYLBCRTRVJP7rrpWNRcxh+VZWWhCopkvUAtXuaAbl1FYh1 fwdyaGYHd5ZNDTiOZdEAOa+jnojtdHSANnRL8C/Z9oHHcRQOb/NUqQb/9z2HQRGSbm5K nz8w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756092761; x=1756697561; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=zoFi0fOoSlOYyhxhA49ewaIAMmopl8uXLyivT/7yV50=; b=fOK7s8G88kA/jBeQ0p0IYH+YibxfTVHn+SXaNdet2sfR/c5w8QJFEQYfF2Wk+HSZmp hXAJ2dt7ZzMBvtCVgybXBqlFnGjy2veQqa0KPPolbj9cpYAw283NGUKVzq3F8yPbrD6D 0qulrBP4L/yHWYpQLa3DSUN5+r4hSMd5c3IN8N3f0jS6rYKduDi6U5FLIwzIpDpRG84V 0u39BvQ1nY66ux/zL8XpQRr5R4kg6l4L7qRtS8DX0JH7tG9vCsaPKsm14JxtxEPy5kvA s2CDVqKqT8+D7W2dXaUS8BOZTGrat1bUzDfFDIsHbXyw7U4aYYQOSsirLoX3FQafwUd3 jzSA== X-Forwarded-Encrypted: i=1; AJvYcCVc6BL/qRqYpVNJGWyJQbWz1tsMasrxzfL+SQAe+pvAf2UEBnfVXVKKYbPuXyz5dFaK5JT8LLllPjJf@vger.kernel.org, AJvYcCVrs8Pw1HKoaQD6CVowMF/U+s84dH0VwrnBhJ3n7ARLh9tecAPOqtBiEMQ9fH8BtXs3v9AXQFPxPjTscQ==@vger.kernel.org X-Gm-Message-State: AOJu0YwWDHrJRCdzVwV8yEQd3oQQhxdWyYeKKBCapemWd//jmP2QbRBG pZOJXb8BaNwZEkLp8xvmoCCi5Xp5ZUU8W2Y9skRxg8s4l8WiUrPx4kDo X-Gm-Gg: ASbGncsaTfE89TJAqZoLa/4nuG+v9QT3bio8EpJ/2NXrIzGV4mXa8X59VIOyjVwoeXO W0diSOcd5r4SvKoKi7s1rkjRocQfJJHPIf1wfNWyQHi9wECuUkdnbg5S5FORxLpVeDz+pvilHTi fxTFkfmk9BEd9AnmkKApNJDt9ms7CQAUfDxPe/W4nvxdwmlNc7zPDguAiv/wx+uEqS97IM5Slq1 gsfc0olFgv0Gtzq1RLSQvEWKZHk+vz1TZ7dg1zvdH+EAP7XJCTNQMdtl1xrWqf9h+NfhYB/ECFK x6OhFCcnvrLPBQmZ1v7wqZq5st+RJpNOx9NUfeEnagTwO+DJTz6gW2YQvzq+sh7CDwoNzwtu8NV ZNPtI5jMno9SQuw2TiRu/uDmz/TaU+g4/joNJ8UseGMxHsRmBJne94EaMG9TsSm3MQCKxrb7mhL Y/Y6Y= X-Google-Smtp-Source: AGHT+IGGTDj2BoZmTjd5+FNXRhmdGpGwZf8/2YTuUw0zGBlQLfEVEwZuvkrM6a+uQ2C9+WQ+5HlElQ== X-Received: by 2002:a05:6214:f2f:b0:707:49e5:fb8 with SMTP id 6a1803df08f44-70d972015cfmr114592906d6.40.1756092760560; Sun, 24 Aug 2025 20:32:40 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-70da7147253sm38205506d6.5.2025.08.24.20.32.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 24 Aug 2025 20:32:40 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Geert Uytterhoeven , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, devicetree@vger.kernel.org, =?UTF-8?q?Andreas=20F=C3=A4rber?= , Conor Dooley Subject: [PATCH v4 1/6] dt-bindings: vendor-prefixes: Add fdhisi, titanmec, princeton, winrise, wxicore Date: Sun, 24 Aug 2025 23:32:27 -0400 Message-ID: <20250825033237.60143-2-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com> References: <20250825033237.60143-1-jefflessard3@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 Add vendor prefixes of chip manufacturers supported by the TM16xx 7-segment LED matrix display controllers driver: - fdhisi: Fuzhou Fuda Hisi Microelectronics Co., Ltd. - titanmec: Shenzhen Titan Micro Electronics Co., Ltd. - princeton: Princeton Technology Corp. - winrise: Shenzhen Winrise Technology Co., Ltd. - wxicore: Wuxi i-Core Electronics Co., Ltd. The titanmec prefix is based on the company's domain name titanmec.com. The remaining prefixes are based on company names, as these manufacturers either lack active .com domains or their .com domains are occupied by unrelated businesses. The fdhisi and titanmec prefixes were originally identified by Andreas F=C3=A4rber. CC: Andreas F=C3=A4rber Acked-by: Conor Dooley Signed-off-by: Jean-Fran=C3=A7ois Lessard --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Docum= entation/devicetree/bindings/vendor-prefixes.yaml index 77160cd47..9fdba2911 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -540,6 +540,8 @@ patternProperties: description: Fastrax Oy "^fcs,.*": description: Fairchild Semiconductor + "^fdhisi,.*": + description: Fuzhou Fuda Hisi Microelectronics Co., Ltd. "^feixin,.*": description: Shenzhen Feixin Photoelectic Co., Ltd "^feiyang,.*": @@ -1233,6 +1235,8 @@ patternProperties: description: Prime View International (PVI) "^primux,.*": description: Primux Trading, S.L. + "^princeton,.*": + description: Princeton Technology Corp. "^probox2,.*": description: PROBOX2 (by W2COMP Co., Ltd.) "^pri,.*": @@ -1567,6 +1571,8 @@ patternProperties: description: Texas Instruments "^tianma,.*": description: Tianma Micro-electronics Co., Ltd. + "^titanmec,.*": + description: Shenzhen Titan Micro Electronics Co., Ltd. "^tlm,.*": description: Trusted Logic Mobility "^tmt,.*": @@ -1724,6 +1730,8 @@ patternProperties: description: Wingtech Technology Co., Ltd. "^winlink,.*": description: WinLink Co., Ltd + "^winrise,.*": + description: Shenzhen Winrise Technology Co., Ltd. "^winsen,.*": description: Winsen Corp. "^winstar,.*": @@ -1740,6 +1748,8 @@ patternProperties: description: Wobo "^wolfvision,.*": description: WolfVision GmbH + "^wxicore,.*": + description: Wuxi i-Core Electronics Co., Ltd. "^x-powers,.*": description: X-Powers "^xen,.*": --=20 2.43.0 From nobody Fri Oct 3 20:53:47 2025 Received: from mail-qk1-f171.google.com (mail-qk1-f171.google.com [209.85.222.171]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A55D227563; Mon, 25 Aug 2025 03:32:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.171 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092767; cv=none; b=JxBv7QBZm5UBtcD+u9NxgF38RY1X9Fzv1+amTkNoekkdFQ3y4mK19cPHi7qf9tvn2p+v6Y6vEdSrRLzNtCWZm6bBtwV2CTISEF99GJosTvBCsDHFIPZj1kI1PSTfMhntxTqx7aQ4sv16vPcDDe89ipJEKJmCpaQOpU+2ulLR4j8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092767; c=relaxed/simple; bh=CdknLA4oYkmiEMfaV6tQtGlShoyMmY/5mdkIN2hxfiY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=dnlNNXs7nFO0PGR2CIsNbVbkeXJ/Al8vs0xD6xYVseJ1EJjeO2M/yOqVJ0yLbi+YPttujM1nUUtv4hupVvy2a+0/Zu5bx8/wUKmOjIw05c26cvw+oA8TKcSDbkXIfLCufZmWBC5Y+aNxVkUIBLVSdlio1okyTWIKlUAv8EJV7e8= 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=H/uwobXn; arc=none smtp.client-ip=209.85.222.171 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="H/uwobXn" Received: by mail-qk1-f171.google.com with SMTP id af79cd13be357-7e87063d4a9so458537185a.2; Sun, 24 Aug 2025 20:32:43 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756092762; x=1756697562; 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=vH0RnFbBr4G+KmlwYqWn9JvCWlzsCGtB0ZNchU5WCIc=; b=H/uwobXnpclHO5IoEEt52ZF782IZfe0mcZ7IU1wspkDNLePnw6pnyqqbeAa4LcENyn LLruikubaJQe/DrNVr8mFSBskokrMqJeel6qSZQk+umCxEwGsOB4xeCs0Bau70tANbqW 8s05uKuUwxQOkgpijUR9Kvx/aZZP1dM+cMQhD9Y0nDOaBWH9KAeFvnn1wLh5Jx/tsTDM 1Sxkr7o+p4gQKgtjnGixtzj1isjza0K/yVPyq/aV/53/Gufc1pwo7UfIYUbS7J7iZuQL bvBBGP+93pUSpKS0vomT1GdmXHfnkBKssf1YKwuarIKe0FlZeT/kT7+LtU8TCyMa1Kar /NgA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756092762; x=1756697562; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=vH0RnFbBr4G+KmlwYqWn9JvCWlzsCGtB0ZNchU5WCIc=; b=HCKVdBtnoQwSJJMHlt7XrDfbMsStca4iNmOW8KWeDVP0e8oIQDFq+503RjDyrWuo4A ZxLIIyHyf5ivcQ5tXq7eWd4P3J6svuWFXCWuLOU8TIubhkgj9w2950zux7joSu8s80YD rMT9weQ/rtRW2WYoJGiQJ///Nsiv4XU0t1HQiz0NfCSqWdmJQeH1XAKLI0zk47np9mia 38MlTm07nDp9UdAXfAu5Qnx5UxUk913vpT6z0V/4de7bjUy0Ovaqo2G8pbs4i8cinz7C h5vZLMo5v5YB+qiM03S/PEEKN7VDM809xay8ArRN1OR2zdjNdvpK9R4cS2+BqBOMWqg1 37gQ== X-Forwarded-Encrypted: i=1; AJvYcCVZUCHKsGA18dXRv3pEDygs+EoMelSZZhCDNGkD/p8nY7ZdGUXvTy8FGqmphbHyIRT+zIO+Lzirpwz+@vger.kernel.org, AJvYcCXk67qUszl/7c83nCTxwdeDoYA1cC+LzH5vdjBgsPMg0MTBpuvoDv5I4NNEG1UR8/tSySyrXru/rk7lDQ==@vger.kernel.org X-Gm-Message-State: AOJu0YxMlR0tZx3VwXMCTyznoYdAkxq3BYU0jTATegCkWBQ9b7oUqNVo 1ex/BXfMLiTaJvD/iFmJvGOjwU364gawN9D4sLouV6q9rGAtQ8u7bNN/tNBQvON0 X-Gm-Gg: ASbGnctpdUryJx8/LSfOGpNbEe87A/oYb0inFszzWO/7Hgol13LZZ9rJTUxS/8U9JMY c99CJ42nxRh6qHjX2KKOsB4h+MHEjvxaOJh8h2JIBAjionYfstd/NMdAuJODBz1Ax3RFhQSXHk5 FWx9Wh/hJ/y/DJ7lV0t+0I58UCnUViWRCyN7Ne+ZKoGNn4BnFYZOiDCImF5pj9BqkSXZH9JAuUK fAEgByx8x/TW3Z7POsCwJHieyDs4zaKOg/l24k7CDSDu0gX2UdB99fckK/CH++M7uiQN395MpvY v4a9Z7RGYW9tYWfXPbBjjS5Gb3rFYp/G5+1xfC/qCnfrgArFUmsDUT83mh9cSVtdL+qaae6NEJF VipFXLydcTgEeWu6dbQ0EBN5wXvTKqN/FmFM3rLDSJagqA4dLHVtBg6HiTi8HSK6S4555 X-Google-Smtp-Source: AGHT+IHzrnRhKFuYRQ0NR2Coi8A3jEzFcY4bFfWs5HAw1xLMT3iznYldxcQPfvJOIQSXh1VbnHizHw== X-Received: by 2002:a05:620a:258f:b0:7ea:1025:efa1 with SMTP id af79cd13be357-7ea10f96f67mr1237298085a.16.1756092761988; Sun, 24 Aug 2025 20:32:41 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id af79cd13be357-7ebed8902fcsm404437185a.20.2025.08.24.20.32.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 24 Aug 2025 20:32:41 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Geert Uytterhoeven , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH v4 2/6] dt-bindings: auxdisplay: add Titan Micro Electronics TM16xx Date: Sun, 24 Aug 2025 23:32:28 -0400 Message-ID: <20250825033237.60143-3-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com> References: <20250825033237.60143-1-jefflessard3@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 Add documentation for TM16xx-compatible 7-segment LED display controllers with keyscan. Signed-off-by: Jean-Fran=C3=A7ois Lessard --- Notes: The 'segments' property is intentionally not vendor-prefixed as it defines a generic hardware description concept applicable to any 7-segment display controller. The property describes the fundamental grid/segment coordinate mapping that is controller-agnostic and could be reused by other LED matrix display bindings. Similar to how 'gpios' describes GPIO connections generically, 'segments' describes segment connections in a standardized way using uint32-matrix format. .../bindings/auxdisplay/titanmec,tm16xx.yaml | 477 ++++++++++++++++++ MAINTAINERS | 5 + 2 files changed, 482 insertions(+) create mode 100644 Documentation/devicetree/bindings/auxdisplay/titanmec,t= m16xx.yaml diff --git a/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.y= aml b/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml new file mode 100644 index 000000000..c94556d95 --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml @@ -0,0 +1,477 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/auxdisplay/titanmec,tm16xx.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Auxiliary displays based on TM16xx and compatible LED controllers + +maintainers: + - Jean-Fran=C3=A7ois Lessard + +description: | + LED matrix controllers used in auxiliary display devices that drive indi= vidual + LED icons and 7-segment digit groups through a grid/segment addressing s= cheme. + Controllers manage a matrix of LEDs organized as grids (columns/banks in + vendor datasheets) and segments (rows/bit positions in vendor datasheets= ). + Maximum grid and segment indices are controller-specific. + + The controller is agnostic of the display layout. Board-specific LED wir= ing is + described through child nodes that specify grid/segment coordinates for + individual icons and segment mapping for 7-segment digits. + + The bindings use separate 'leds' and 'digits' containers to accommodate + different addressing schemes: + - LEDs use 2-cell addressing (grid, segment) for matrix coordinates + - Digits use 1-cell addressing with explicit segment mapping + + The controller node exposes a logical LED-like control for the aggregate + display brightness. Child nodes describe individual icons and 7-seg digi= ts. + The top-level control supports only label and brightness-related propert= ies + and does not support other common LED properties such as color or functi= on. + Child LED nodes use the standard LED binding. + + Optional keypad scanning is supported when both 'linux,keymap' and + 'poll-interval' properties are specified. + +properties: + compatible: + oneOf: + - items: + - enum: + - fdhisi,fd628 + - princeton,pt6964 + - wxicore,aip1628 + - const: titanmec,tm1628 + - items: + - enum: + - wxicore,aip1618 + - const: titanmec,tm1618 + - items: + - enum: + - fdhisi,fd650 + - wxicore,aip650 + - const: titanmec,tm1650 + - enum: + - fdhisi,fd620 + - fdhisi,fd655 + - fdhisi,fd6551 + - titanmec,tm1618 + - titanmec,tm1620 + - titanmec,tm1628 + - titanmec,tm1638 + - titanmec,tm1650 + - winrise,hbs658 + + reg: + maxItems: 1 + + label: + description: + The label for the top-level LED. If omitted, the label is taken from= the + node name (excluding the unit address). It has to uniquely identify a + device, i.e. no other LED class device can be assigned the same labe= l. + $ref: /schemas/leds/common.yaml#/properties/label + + max-brightness: + description: + Normally the maximum brightness is determined by the hardware and th= is + property is not required. This property is used to put a software li= mit + on the brightness apart from what the driver says, as it could happen + that a LED can be made so bright that it gets damaged or causes dama= ge + due to restrictions in a specific system, such as mounting condition= s. + $ref: /schemas/leds/common.yaml#/properties/max-brightness + + default-brightness: + description: + Brightness to be set if LED's default state is on. Used only during + initialization. If the option is not set then max brightness is used. + $ref: /schemas/types.yaml#/definitions/uint32 + + digits: + type: object + description: Container for 7-segment digit group definitions + additionalProperties: false + + properties: + "#address-cells": + const: 1 + "#size-cells": + const: 0 + + patternProperties: + "^digit@[0-9]+$": + type: object + unevaluatedProperties: false + + properties: + reg: + description: Digit position identifier + maxItems: 1 + + segments: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + description: | + Array of grid/segment coordinate pairs for each 7-segment po= sition. + Each entry is mapping to standard 7-segment p= ositions + in order: a, b, c, d, e, f, g + + Standard 7-segment layout: + aaa + f b + f b + ggg + e c + e c + ddd + items: + items: + - description: Grid index + - description: Segment index + minItems: 7 + maxItems: 7 + + required: + - reg + - segments + + leds: + type: object + description: Container for individual LED icon definitions + additionalProperties: false + + properties: + "#address-cells": + const: 2 + "#size-cells": + const: 0 + + patternProperties: + "^led@[0-9]+,[0-9]+$": + type: object + $ref: /schemas/leds/common.yaml# + unevaluatedProperties: false + + properties: + reg: + description: + Grid and segment indices as of this individua= l LED icon + + required: + - reg + +allOf: + - $ref: /schemas/input/input.yaml# + - $ref: /schemas/input/matrix-keymap.yaml# + +dependencies: + poll-interval: + - linux,keymap + linux,keymap: + - poll-interval + autorepeat: + - linux,keymap + - poll-interval + +# SPI controllers require 3-wire (combined MISO/MOSI line) +if: + properties: + compatible: + contains: + enum: + - fdhisi,fd620 + - fdhisi,fd628 + - princeton,pt6964 + - titanmec,tm1618 + - titanmec,tm1620 + - titanmec,tm1628 + - titanmec,tm1638 + - wxicore,aip1618 + - wxicore,aip1628 +then: + allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + properties: + spi-3wire: true + required: + - spi-3wire + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + + // I2C example: Magicsee N5 TV box with fd655 controller + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + display@24 { + reg =3D <0x24>; + compatible =3D "fdhisi,fd655"; + + digits { + #address-cells =3D <1>; + #size-cells =3D <0>; + + digit@0 { + reg =3D <0>; + segments =3D <4 3>, <4 4>, <4 5>, <4 0>, <4 1>, <4 2>, <4 6>; + }; + + digit@1 { + reg =3D <1>; + segments =3D <3 3>, <3 4>, <3 5>, <3 0>, <3 1>, <3 2>, <3 6>; + }; + + digit@2 { + reg =3D <2>; + segments =3D <2 3>, <2 4>, <2 5>, <2 0>, <2 1>, <2 2>, <2 6>; + }; + + digit@3 { + reg =3D <3>; + segments =3D <1 3>, <1 4>, <1 5>, <1 0>, <1 1>, <1 2>, <1 6>; + }; + }; + + leds { + #address-cells =3D <2>; + #size-cells =3D <0>; + + led@0,0 { + reg =3D <0 0>; + function =3D LED_FUNCTION_ALARM; + }; + + led@0,1 { + reg =3D <0 1>; + function =3D LED_FUNCTION_USB; + }; + + led@0,2 { + reg =3D <0 2>; + function =3D "play"; + }; + + led@0,3 { + reg =3D <0 3>; + function =3D "pause"; + }; + + led@0,4 { + reg =3D <0 4>; + function =3D "colon"; + }; + + led@0,5 { + reg =3D <0 5>; + function =3D LED_FUNCTION_LAN; + }; + + led@0,6 { + reg =3D <0 6>; + function =3D LED_FUNCTION_WLAN; + }; + }; + }; + }; + + - | + #include + + // SPI example: TM1638 module with keypad support + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + display@0 { + reg =3D <0>; + compatible =3D "titanmec,tm1638"; + spi-3wire; + spi-lsb-first; + spi-max-frequency =3D <500000>; + + label =3D "tm1638"; + default-brightness =3D <2>; + max-brightness =3D <4>; + poll-interval =3D <100>; + linux,keymap =3D ; + + digits { + #address-cells =3D <1>; + #size-cells =3D <0>; + + digit@0 { + reg =3D <0>; + segments =3D <7 0>, <7 1>, <7 2>, <7 3>, <7 4>, <7 5>, <7 6>; + }; + + digit@1 { + reg =3D <1>; + segments =3D <6 0>, <6 1>, <6 2>, <6 3>, <6 4>, <6 5>, <6 6>; + }; + + digit@2 { + reg =3D <2>; + segments =3D <5 0>, <5 1>, <5 2>, <5 3>, <5 4>, <5 5>, <5 6>; + }; + + digit@3 { + reg =3D <3>; + segments =3D <4 0>, <4 1>, <4 2>, <4 3>, <4 4>, <4 5>, <4 6>; + }; + + digit@4 { + reg =3D <4>; + segments =3D <3 0>, <3 1>, <3 2>, <3 3>, <3 4>, <3 5>, <3 6>; + }; + + digit@5 { + reg =3D <5>; + segments =3D <2 0>, <2 1>, <2 2>, <2 3>, <2 4>, <2 5>, <2 6>; + }; + + digit@6 { + reg =3D <6>; + segments =3D <1 0>, <1 1>, <1 2>, <1 3>, <1 4>, <1 5>, <1 6>; + }; + + digit@7 { + reg =3D <7>; + segments =3D <0 0>, <0 1>, <0 2>, <0 3>, <0 4>, <0 5>, <0 6>; + }; + }; + + leds { + #address-cells =3D <2>; + #size-cells =3D <0>; + + led@0,7 { + reg =3D <0 7>; + }; + + led@1,7 { + reg =3D <1 7>; + }; + + led@2,7 { + reg =3D <2 7>; + }; + + led@3,7 { + reg =3D <3 7>; + }; + + led@4,7 { + reg =3D <4 7>; + }; + + led@5,7 { + reg =3D <5 7>; + }; + + led@6,7 { + reg =3D <6 7>; + }; + + led@7,7 { + reg =3D <7 7>; + }; + }; + }; + }; + + - | + #include + + // SPI example: X96 Max with transposed layout (fd628 with tm1628 fall= back) + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + display@0 { + reg =3D <0>; + compatible =3D "fdhisi,fd628", "titanmec,tm1628"; + spi-3wire; + spi-lsb-first; + spi-max-frequency =3D <500000>; + + digits { + #address-cells =3D <1>; + #size-cells =3D <0>; + + digit@0 { + reg =3D <0>; + segments =3D <0 3>, <1 3>, <2 3>, <3 3>, <4 3>, <5 3>, <6 3>; + }; + + digit@1 { + reg =3D <1>; + segments =3D <0 2>, <1 2>, <2 2>, <3 2>, <4 2>, <5 2>, <6 2>; + }; + + digit@2 { + reg =3D <2>; + segments =3D <0 1>, <1 1>, <2 1>, <3 1>, <4 1>, <5 1>, <6 1>; + }; + + digit@3 { + reg =3D <3>; + segments =3D <0 0>, <1 0>, <2 0>, <3 0>, <4 0>, <5 0>, <6 0>; + }; + }; + + leds { + #address-cells =3D <2>; + #size-cells =3D <0>; + + led@0,4 { + reg =3D <0 4>; + function =3D "apps"; + }; + + led@1,4 { + reg =3D <1 4>; + function =3D "setup"; + }; + + led@2,4 { + reg =3D <2 4>; + function =3D LED_FUNCTION_USB; + }; + + led@3,4 { + reg =3D <3 4>; + function =3D LED_FUNCTION_SD; + }; + + led@4,4 { + reg =3D <4 4>; + function =3D "colon"; + }; + + led@5,4 { + reg =3D <5 4>; + function =3D "hdmi"; + }; + + led@6,4 { + reg =3D <6 4>; + function =3D "video"; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index daf520a13..4e5a7db6d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25402,6 +25402,11 @@ W: http://sourceforge.net/projects/tlan/ F: Documentation/networking/device_drivers/ethernet/ti/tlan.rst F: drivers/net/ethernet/ti/tlan.* =20 +TM16XX-COMPATIBLE LED CONTROLLERS DISPLAY DRIVER +M: Jean-Fran=C3=A7ois Lessard +S: Maintained +F: Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml + TMIO/SDHI MMC DRIVER M: Wolfram Sang L: linux-mmc@vger.kernel.org --=20 2.43.0 From nobody Fri Oct 3 20:53:47 2025 Received: from mail-qt1-f176.google.com (mail-qt1-f176.google.com [209.85.160.176]) (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 0955C243968; Mon, 25 Aug 2025 03:32:44 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092768; cv=none; b=rHrdTZheL8gP5F7fzPZogwFoCeI1xdkYCtizmn2Q+D7BPPapWcGMGapWlnAHqxlOLQH2Dxs8DICR9z09whOVRPP9N9MZO90KEL1UDNheRdX/NAAoFTFSyfg/MRG6PhQl8DTy6cE9M+3sMi2LCg54RzapbvcYcK7m8pmZcW4J7xI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092768; c=relaxed/simple; bh=GpPJOp/sP3uGgfOfD6oz3FmYVJ4yKf+ZJZKZv72/G6E=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=JeZJHwPnDvPG9IPSf0mNe5aE984E7/CYHPClYx5p9AgsCdKV8qdhhi5dk7DdL3QpCnvIXO4Kx3n0hM5tDannFJqUkapALdkIcGFCZ3rlVnfJgz4727A0HIp9WouWqK2uMPMDmZQcU6zfar/OlQ1VEu0q2vekM4RzTI6EttWLLME= 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=CxPnpiVp; arc=none smtp.client-ip=209.85.160.176 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="CxPnpiVp" Received: by mail-qt1-f176.google.com with SMTP id d75a77b69052e-4b2d501db08so5004031cf.3; Sun, 24 Aug 2025 20:32:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756092764; x=1756697564; 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=6xy/XO0wCzzN9qB0m2Lh+Z8NvY6FhR7tENrvLTwy0ko=; b=CxPnpiVpJ+W8ttW7J8YT7q0jYQh5hHFgitmCrvQyA3iNzDXO5z5MIRaFcyF87jQATn 0/du4RNiJ4Uv1/Lic9qOjywCz2Ram4rV3uYXHSZE5YZQQV3aMy/fW3nb4WGWR5joKHb9 F/Gjj+qL4ql8xqYfA/fjssU/xfhzWtoq0gFCk+nSjGOsPvCyNu63M81xhsTi7r3kgk2n REBkYrJEl9vCYb/jyWM4IoxOanWI5mE42oiIAcehnCrxztGPsFs7xmLHP1Q6LGXWyMh3 leQh8/mEbQOow//48lfc0BBUoIAsdmLCqEqmKNA6GyIfhRfvFQqb7753X/uYxRqnV67t wquQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756092764; x=1756697564; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=6xy/XO0wCzzN9qB0m2Lh+Z8NvY6FhR7tENrvLTwy0ko=; b=dqfm6e/aiJCAWFeIbAQBiX5M7MG6hir1Emgb3TSjg9zLRlr3Qmlfi8j3yHZcbGSka+ sY3P/qlOm1tsyugYQHVi01U6HXMnttpvQkonsOhfbnSIU0UUpGS0z5nW6tzleWmtB9Ua nBbkP8xqcbm7t8Xr/41NhITqdgxHt4H0t9RP1rUrAihxLTN1b4s3VGRMKqbU4bKDHlRU SnEs2Ve/DMNrzzp8uiKA7gFCU4x2c1SeeOlQ87RHUjfvkviByLA9EmknJPZlAVwA739c PeDC3/gmtCoKRg1v1KNbEjrwVqZePKS/gBtDAa4c2sHdFi6lCqx5LIqusmprnA7F4dSC b7VQ== X-Forwarded-Encrypted: i=1; AJvYcCXXadh7dp4TAuhU5dzs7cT18S34px9MkUIxXz7UYOISxJnBE3/Ymz6QmIoUd7vMjMeFhXyedCLshQLF@vger.kernel.org, AJvYcCXoLbvZCoqpjLe3RBO7Rq/ErlLUOpLNb6/p5t2EAQcN2ZjwI4SqihKow74c8Tv5LFVHwjUXkMypiS5W+g==@vger.kernel.org X-Gm-Message-State: AOJu0Yy5hCBkK2jExZhj5hTRXyN7iZkazONfJq5PT4/a33opHE2RssK6 +znWZ8+iwAL+v3SPNw2POpkLA3SajWoV4i60PdNfq1x4rfSno7yNvyC8Ykp29YiV X-Gm-Gg: ASbGnctDKUljRmrhNvlKrQBQ4PVB0c5APCJEAwGgWf6MvILMzsB5T/ldJMu2/Zun9ie NI5OgEvl/RLdUNK97y3l5rIcEt5pU2lCQS8icKV0H8KBDurfm7yq6dtZil1hRh8E2NY/1IkeONw amasjhnYtn4LWufK8zfQq0gnE6P8mpDa1x3w49gQQn2dUvwguiRmrlLuZ2wdD5xxTu9ZMe7qeri hqFNLOeZ0JvNzVxQtbQIW2QhoqcJB7FmsZ7X1iMDtoFEt7/YRsR+8bqilr5RSbF7cISt0kBFOjD 9bMBzWvXSGsI3CeMG8niCUBe65GoY7cQiFaoGKXHjVzBT073IUPQJzFGAkEiPZmxn4CUWzbjCoo vguBNEMvVFUn9ZQBfVzZoaItHPM+KK0E/fTa+IFyPWaRQT2Dlr4RUcsG2zsa9h9pnCGCl X-Google-Smtp-Source: AGHT+IGdLGR3buHUeCZQPsPnHGE1ip483AQhzZLezduvp1cUsw0HZphDbEHVObFLJPBKodAjxfovsQ== X-Received: by 2002:a05:620a:d93:b0:7e8:2c81:6f13 with SMTP id af79cd13be357-7ea10e8f3a6mr1217902585a.0.1756092763354; Sun, 24 Aug 2025 20:32:43 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id af79cd13be357-7ebed8911aesm400501685a.19.2025.08.24.20.32.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 24 Aug 2025 20:32:42 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Geert Uytterhoeven , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, devicetree@vger.kernel.org, Paolo Sabatino , Christian Hewitt , Boris Gjenero , Martin Blumenstingl Subject: [PATCH v4 3/6] auxdisplay: Add TM16xx 7-segment LED matrix display controllers driver Date: Sun, 24 Aug 2025 23:32:29 -0400 Message-ID: <20250825033237.60143-4-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com> References: <20250825033237.60143-1-jefflessard3@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 Add driver for TM16xx family LED controllers and compatible chips from multiple vendors including Titan Micro, Fuda Hisi, i-Core, Princeton, and Winrise. These controllers drive 7-segment digits and individual LED icons through either I2C or SPI buses. Successfully tested on various ARM TV boxes including H96 Max, Magicsee N5, Tanix TX3 Mini, Tanix TX6, X92, and X96 Max across different SoC platforms (Rockchip, Amlogic, Allwinner). Acked-by: Paolo Sabatino # As primary user, inte= grated tm16xx into Armbian rockchip64 Acked-by: Christian Hewitt # As primary user, = integrated tm16xx into LibreElec Tested-by: Boris Gjenero # Tested on X92 Tested-by: Paolo Sabatino # Tested on H96 Max (X= Y_RK3328) Tested-by: Christian Hewitt # Tested on X96 Ma= x, Tanix TX3 Mini Tested-by: Martin Blumenstingl # Teste= d on Tanix TX3 Mini Signed-off-by: Jean-Fran=C3=A7ois Lessard --- Notes: checkpatch reports false positives that are intentionally ignored: DEVICE_ATTR_FUNCTIONS: Functions are correctly prefixed with driver name (tm16xx_*) following standard kernel practice for device attribute functions to avoid namespace conflicts. BIT_MACRO: bit shifts are used for field values while GENMASK/BIT are used for bit positions per semantic convention =20 include is required for the default name of the main led device, excluding the unit address, as implemented in drivers/leds/led-core.c which relies on of_node->name =20 LED registration uses non-devm variant on-purpose to allow explicit unregistration on device removal, ensuring LED triggers are immediately stopped. This prevents stale LED trigger activity from continuing after the hardware is gone, avoiding the need for complex state tracking in brightness callbacks. .../ABI/testing/sysfs-class-leds-tm16xx | 57 ++ MAINTAINERS | 3 + drivers/auxdisplay/Kconfig | 20 +- drivers/auxdisplay/Makefile | 2 + drivers/auxdisplay/tm16xx.h | 177 +++++ drivers/auxdisplay/tm16xx_core.c | 618 ++++++++++++++++++ 6 files changed, 876 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-class-leds-tm16xx create mode 100644 drivers/auxdisplay/tm16xx.h create mode 100644 drivers/auxdisplay/tm16xx_core.c diff --git a/Documentation/ABI/testing/sysfs-class-leds-tm16xx b/Documentat= ion/ABI/testing/sysfs-class-leds-tm16xx new file mode 100644 index 000000000..c01cca251 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-leds-tm16xx @@ -0,0 +1,57 @@ +What: /sys/class/leds//value +Date: August 2025 +KernelVersion: 6.17 +Contact: Jean-Fran=C3=A7ois Lessard +Description: + Controls the text displayed on the TM16xx 7-segment display. + + Reading returns the current display content as ASCII characters, + one character per digit position, followed by a newline. + + Writing sets new display content. Input characters are mapped + to 7-segment patterns using the configured character map. The + string length should not exceed the number of available digits + (see num_digits). Shorter strings will clear remaining digits. + + Example: + echo "1234" > value # Display "1234" + cat value # Returns "1234\n" + +What: /sys/class/leds//num_digits +Date: August 2025 +KernelVersion: 6.17 +Contact: Jean-Fran=C3=A7ois Lessard +Description: + Read-only attribute showing the number of 7-segment digit + positions available on this TM16xx display controller. + + The value is determined by the device tree configuration + and represents the maximum length for the 'value' attribute. + + Example: + cat num_digits # Returns "4\n" for 4-digit display + +What: /sys/class/leds//map_seg7 +Date: August 2025 +KernelVersion: 6.17 +Contact: Jean-Fran=C3=A7ois Lessard +Description: + Read/write binary blob representing the ASCII-to-7-segment + display conversion table used by the TM16xx driver, as defined + by struct seg7_conversion_map in . + + This attribute is not human-readable. Writes must match the + struct size exactly, else -EINVAL is returned; reads return the + entire mapping as a binary blob. + + This interface and its implementation match existing conventions + used in auxdisplay and segment-mapped display drivers since 2005. + + ABI note: This style of binary sysfs attribute *is an exception* + to current "one value per file, text only" sysfs rules, for + historical compatibility and driver uniformity. New drivers are + discouraged from introducing additional binary sysfs ABIs. + + Reference interface guidance: + - include/uapi/linux/map_to_7segment.h +Users: Display configuration utilities and embedded system scripts/tools. diff --git a/MAINTAINERS b/MAINTAINERS index 4e5a7db6d..0ed971881 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25405,7 +25405,10 @@ F: drivers/net/ethernet/ti/tlan.* TM16XX-COMPATIBLE LED CONTROLLERS DISPLAY DRIVER M: Jean-Fran=C3=A7ois Lessard S: Maintained +F: Documentation/ABI/testing/sysfs-class-leds-tm16xx F: Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml +F: drivers/auxdisplay/tm16xx.h +F: drivers/auxdisplay/tm16xx_core.c =20 TMIO/SDHI MMC DRIVER M: Wolfram Sang diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index bedc6133f..7b58c6cc8 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -316,7 +316,7 @@ endif # PARPORT_PANEL =20 config PANEL_CHANGE_MESSAGE bool "Change LCD initialization message ?" - depends on CHARLCD || LINEDISP + depends on CHARLCD || LINEDISP || TM16XX help This allows you to replace the boot message indicating the kernel versi= on and the driver version with a custom message. This is useful on applian= ces @@ -526,6 +526,24 @@ config SEG_LED_GPIO This driver can also be built as a module. If so, the module will be called seg-led-gpio. =20 +config TM16XX + tristate "TM16XX-compatible 7-segment LED controllers" + depends on SPI || I2C + select NEW_LEDS + select LEDS_CLASS + select LEDS_TRIGGERS + help + This driver supports the following TM16XX compatible + I2C and SPI 7-segment led display chips: + - Titanmec: TM1618, TM1620, TM1628, TM1638, TM1650 + - Fuda Hisi: FD620, FD628, FD650, FD655, FD6551 + - i-Core: AiP650, AiP1618, AiP1628 + - Princeton: PT6964 + - Winrise: HBS658 + + This driver can also be built as a module. If so, the module + will be called tm16xx. + # # Character LCD with non-conforming interface section # diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index f5c13ed1c..7ecf3cd4a 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -16,3 +16,5 @@ obj-$(CONFIG_LINEDISP) +=3D line-display.o obj-$(CONFIG_MAX6959) +=3D max6959.o obj-$(CONFIG_PARPORT_PANEL) +=3D panel.o obj-$(CONFIG_SEG_LED_GPIO) +=3D seg-led-gpio.o +obj-$(CONFIG_TM16XX) +=3D tm16xx.o +tm16xx-y +=3D tm16xx_core.o diff --git a/drivers/auxdisplay/tm16xx.h b/drivers/auxdisplay/tm16xx.h new file mode 100644 index 000000000..a7ce483d3 --- /dev/null +++ b/drivers/auxdisplay/tm16xx.h @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * TM16xx and compatible LED display/keypad controller driver + * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips. + * + * Copyright (C) 2024 Jean-Fran=C3=A7ois Lessard + */ + +#ifndef _TM16XX_H +#define _TM16XX_H + +#include +#include +#include +#include + +#define TM16XX_DIGIT_SEGMENTS 7 + +/* Common bit field definitions */ + +/* Command type bits (bits 7-6) */ +#define TM16XX_CMD_MASK GENMASK(7, 6) +#define TM16XX_CMD_MODE (0 << 6) +#define TM16XX_CMD_DATA (1 << 6) +#define TM16XX_CMD_CTRL (2 << 6) +#define TM16XX_CMD_ADDR (3 << 6) +#define TM16XX_CMD_WRITE (TM16XX_CMD_DATA | TM16XX_DATA_MODE_WRITE) +#define TM16XX_CMD_READ (TM16XX_CMD_DATA | TM16XX_DATA_MODE_READ) + +/* Mode command grid settings (bits 1-0) */ +#define TM16XX_MODE_GRID_MASK GENMASK(1, 0) +#define TM16XX_MODE_4GRIDS (0 << 0) +#define TM16XX_MODE_5GRIDS (1 << 0) +#define TM16XX_MODE_6GRIDS (2 << 0) +#define TM16XX_MODE_7GRIDS (3 << 0) + +/* Data command settings */ +#define TM16XX_DATA_ADDR_MASK BIT(2) +#define TM16XX_DATA_ADDR_AUTO (0 << 2) +#define TM16XX_DATA_ADDR_FIXED (1 << 2) +#define TM16XX_DATA_MODE_MASK GENMASK(1, 0) +#define TM16XX_DATA_MODE_WRITE (0 << 0) +#define TM16XX_DATA_MODE_READ (2 << 0) + +/* Control command settings */ +#define TM16XX_CTRL_BR_MASK GENMASK(2, 0) +#define TM16XX_CTRL_ON (1 << 3) + +/* TM1618 specific constants */ +#define TM1618_BYTE1_MASK GENMASK(4, 0) +#define TM1618_BYTE2_MASK GENMASK(7, 5) +#define TM1618_BYTE2_SHIFT 3 +#define TM1618_KEY_READ_LEN 3 +#define TM1618_KEY_MASK (BIT(4) | BIT(1)) + +/* TM1628 specific constants */ +#define TM1628_BYTE1_MASK GENMASK(7, 0) +#define TM1628_BYTE2_MASK GENMASK(13, 8) +#define TM1628_KEY_READ_LEN 5 +#define TM1628_KEY_MASK (GENMASK(4, 3) | GENMASK(1, 0)) + +/* TM1638 specific constants */ +#define TM1638_KEY_READ_LEN 4 +#define TM1638_KEY_MASK (GENMASK(6, 4) | GENMASK(2, 0)) + +/* FD620 specific constants */ +#define FD620_BYTE1_MASK GENMASK(6, 0) + +#define FD620_BYTE2_MASK BIT(7) +#define FD620_BYTE2_SHIFT 5 +#define FD620_KEY_READ_LEN 4 +#define FD620_KEY_MASK (BIT(3) | BIT(0)) + +/* I2C controller addresses and control settings */ +#define TM1650_CMD_CTRL 0x48 +#define TM1650_CMD_READ 0x4F +#define TM1650_CMD_ADDR 0x68 +#define TM1650_CTRL_BR_MASK GENMASK(6, 4) +#define TM1650_CTRL_ON (1 << 0) +#define TM1650_CTRL_SLEEP (1 << 2) +#define TM1650_CTRL_SEG_MASK BIT(3) +#define TM1650_CTRL_SEG8_MODE (0 << 3) +#define TM1650_CTRL_SEG7_MODE (1 << 3) +#define TM1650_KEY_ROW_MASK GENMASK(1, 0) +#define TM1650_KEY_COL_MASK GENMASK(5, 3) +#define TM1650_KEY_DOWN_MASK BIT(6) +#define TM1650_KEY_COMBINED GENMASK(5, 3) + +#define FD655_CMD_CTRL 0x48 +#define FD655_CMD_ADDR 0x66 +#define FD655_CTRL_BR_MASK GENMASK(6, 5) +#define FD655_CTRL_ON (1 << 0) + +#define FD6551_CMD_CTRL 0x48 +#define FD6551_CTRL_BR_MASK GENMASK(3, 1) +#define FD6551_CTRL_ON (1 << 0) + +#define HBS658_KEY_COL_MASK GENMASK(7, 5) + +#define TM16XX_CTRL_BRIGHTNESS(on, val, prfx) \ + ((on) ? (FIELD_PREP(prfx##_CTRL_BR_MASK, (val)) | prfx##_CTRL_ON) : 0) + +/* Forward declarations */ +struct tm16xx_display; +struct tm16xx_digit; +struct tm16xx_led; + +/** + * DOC: struct tm16xx_controller - Controller-specific operations and limi= ts + * @max_grids: Maximum number of grids supported by the controller. + * @max_segments: Maximum number of segments supported by the controller. + * @max_brightness: Maximum brightness level supported by the controller. + * @max_key_rows: Maximum number of key input rows supported by the contro= ller. + * @max_key_cols: Maximum number of key input columns supported by the con= troller. + * @init: Pointer to controller mode/brightness configuration function. + * @data: Pointer to function writing display data to the controller. + * @keys: Pointer to function reading controller key state into bitmap. + * + * Holds function pointers and limits for controller-specific operations. + */ +struct tm16xx_controller { + const u8 max_grids; + const u8 max_segments; + const u8 max_brightness; + const u8 max_key_rows; + const u8 max_key_cols; + int (*const init)(struct tm16xx_display *display); + int (*const data)(struct tm16xx_display *display, u8 index, + unsigned int grid); + int (*const keys)(struct tm16xx_display *display); +}; + +/** + * struct tm16xx_display - Main driver structure for the display + * @dev: Pointer to device struct. + * @controller: Controller-specific function table and limits. + * @client: Union of I2C and SPI client pointers. + * @spi_buffer: DMA-safe buffer for SPI transactions, or NULL for I2C. + * @num_grids: Number of controller grids in use. + * @num_segments: Number of controller segments in use. + * @main_led: LED class device for the entire display. + * @leds: Array of individual LED icon structures. + * @num_leds: Number of individual LED icons. + * @digits: Array of 7-segment digit structures. + * @num_digits: Number of 7-segment digits. + * @flush_init: Work struct for configuration update. + * @flush_display: Work struct for display update. + * @flush_status: Status/result of last flush work. + * @lock: Mutex protecting concurrent access to work operations. + * @state: Bitmap holding current raw display state. + */ +struct tm16xx_display { + struct device *dev; + const struct tm16xx_controller *controller; + union { + struct i2c_client *i2c; + struct spi_device *spi; + } client; + u8 *spi_buffer; + u8 num_grids; + u8 num_segments; + struct led_classdev main_led; + struct tm16xx_led *leds; + u8 num_leds; + struct tm16xx_digit *digits; + u8 num_digits; + struct work_struct flush_init; + struct work_struct flush_display; + int flush_status; + struct mutex lock; /* prevents concurrent work operations */ + unsigned long *state; +}; + +int tm16xx_probe(struct tm16xx_display *display); +void tm16xx_remove(struct tm16xx_display *display); + +#endif /* _TM16XX_H */ diff --git a/drivers/auxdisplay/tm16xx_core.c b/drivers/auxdisplay/tm16xx_c= ore.c new file mode 100644 index 000000000..415be7747 --- /dev/null +++ b/drivers/auxdisplay/tm16xx_core.c @@ -0,0 +1,618 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TM16xx and compatible LED display/keypad controller driver + * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips. + * + * Copyright (C) 2024 Jean-Fran=C3=A7ois Lessard + */ + +#include +#include +#include +#include + +#include "tm16xx.h" + +static const char *tm16xx_init_value; +#ifdef CONFIG_PANEL_BOOT_MESSAGE +tm16xx_init_value =3D CONFIG_PANEL_BOOT_MESSAGE; +#endif + +static SEG7_CONVERSION_MAP(map_seg7, MAP_ASCII7SEG_ALPHANUM); + +/** + * struct tm16xx_led - Individual LED icon mapping + * @cdev: LED class device for sysfs interface. + * @grid: Controller grid index of the LED. + * @segment: Controller segment index of the LED. + */ +struct tm16xx_led { + struct led_classdev cdev; + u8 grid; + u8 segment; +}; + +/** + * struct tm16xx_digit_segment - Digit segment mapping to display coordina= tes + * @grid: Controller grid index for this segment. + * @segment: Controller segment index for this segment. + */ +struct tm16xx_digit_segment { + u8 grid; + u8 segment; +}; + +/** + * struct tm16xx_digit - 7-segment digit mapping and value + * @segments: Array mapping each 7-segment position to a grid/segment on t= he controller. + * @value: Current character value displayed on this digit. + */ +struct tm16xx_digit { + struct tm16xx_digit_segment segments[TM16XX_DIGIT_SEGMENTS]; + char value; +}; + +/* state bitmap helpers */ +/** + * tm16xx_led_nbits() - Number of bits used for the display state bitmap + * @display: pointer to tm16xx_display + * + * Return: total bits in the display state bitmap (grids * segments) + */ +static inline unsigned int tm16xx_led_nbits(const struct tm16xx_display *d= isplay) +{ + return display->num_grids * display->num_segments; +} + +/** + * tm16xx_set_seg() - Set the display state for a specific grid/segment + * @display: pointer to tm16xx_display + * @grid: grid index + * @seg: segment index + * @on: true to turn on, false to turn off + */ +static inline void tm16xx_set_seg(const struct tm16xx_display *display, + const u8 grid, const u8 seg, const bool on) +{ + assign_bit(grid * display->num_segments + seg, display->state, on); +} + +/** + * tm16xx_get_grid() - Get the current segment pattern for a grid + * @display: pointer to tm16xx_display + * @index: grid index + * + * Return: bit pattern of all segments for the given grid + */ +static inline unsigned int tm16xx_get_grid(const struct tm16xx_display *di= splay, + const unsigned int index) +{ + return bitmap_read(display->state, index * display->num_segments, + display->num_segments); +} + +/* main display */ +/** + * tm16xx_display_flush_init() - Workqueue to configure controller and set= brightness + * @work: pointer to work_struct + */ +static void tm16xx_display_flush_init(struct work_struct *work) +{ + struct tm16xx_display *display =3D container_of(work, + struct tm16xx_display, + flush_init); + int ret; + + if (display->controller->init) { + dev_dbg(display->dev, "Configuring controller\n"); + scoped_guard(mutex, &display->lock) { + ret =3D display->controller->init(display); + display->flush_status =3D ret; + } + if (ret < 0) + dev_err(display->dev, + "Failed to configure controller: %d\n", ret); + } +} + +/** + * tm16xx_display_flush_data() - Workqueue to update display data to contr= oller + * @work: pointer to work_struct + */ +static void tm16xx_display_flush_data(struct work_struct *work) +{ + struct tm16xx_display *display =3D container_of(work, + struct tm16xx_display, + flush_display); + int i, ret =3D 0; + unsigned int grid; + + dev_dbg(display->dev, "Sending data to controller\n"); + scoped_guard(mutex, &display->lock) { + if (display->controller->data) { + for (i =3D 0; i < display->num_grids; i++) { + grid =3D tm16xx_get_grid(display, i); + ret =3D display->controller->data(display, i, + grid); + if (ret < 0) { + dev_err(display->dev, + "Failed to write display data: %d\n", + ret); + break; + } + } + } + + display->flush_status =3D ret; + } +} + +/** + * tm16xx_brightness_set() - Set display main LED brightness + * @led_cdev: pointer to led_classdev + * @brightness: new brightness value + */ +static void tm16xx_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct tm16xx_display *display =3D dev_get_drvdata(led_cdev->dev->parent); + + dev_dbg(display->dev, "Setting brightness to %d\n", brightness); + led_cdev->brightness =3D brightness; + schedule_work(&display->flush_init); +} + +/** + * tm16xx_led_set() - Set state of an individual LED icon + * @led_cdev: pointer to led_classdev + * @value: new brightness (0/1) + */ +static void tm16xx_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct tm16xx_led *led =3D container_of(led_cdev, struct tm16xx_led, cdev= ); + struct tm16xx_display *display =3D dev_get_drvdata(led_cdev->dev->parent); + + dev_dbg(display->dev, "Setting led %u,%u to %d\n", led->grid, + led->segment, value); + + tm16xx_set_seg(display, led->grid, led->segment, value); + schedule_work(&display->flush_display); +} + +/** + * tm16xx_value_show() - Sysfs: show current display digit values + * @dev: pointer to device + * @attr: device attribute (unused) + * @buf: output buffer + * + * Return: number of bytes written to output buffer + */ +static ssize_t tm16xx_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev =3D dev_get_drvdata(dev); + struct tm16xx_display *display =3D dev_get_drvdata(led_cdev->dev->parent); + int i; + + for (i =3D 0; i < display->num_digits && i < PAGE_SIZE - 1; i++) + buf[i] =3D display->digits[i].value; + + buf[i++] =3D '\n'; + return i; +} + +/** + * tm16xx_value_store() - Sysfs: set display digit values + * @dev: pointer to device + * @attr: device attribute (unused) + * @buf: new digit values (ASCII chars) + * @count: buffer length + * + * Return: number of bytes written or negative error code + */ +static ssize_t tm16xx_value_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev =3D dev_get_drvdata(dev); + struct tm16xx_display *display =3D dev_get_drvdata(led_cdev->dev->parent); + struct tm16xx_digit *digit; + struct tm16xx_digit_segment *ds; + int i, j; + int seg_pattern; + bool val; + + dev_dbg(display->dev, "Setting value to %s\n", buf); + + for (i =3D 0; i < display->num_digits && i < count; i++) { + digit =3D &display->digits[i]; + digit->value =3D buf[i]; + seg_pattern =3D map_to_seg7(&map_seg7, digit->value); + + for (j =3D 0; j < TM16XX_DIGIT_SEGMENTS; j++) { + ds =3D &digit->segments[j]; + val =3D seg_pattern & BIT(j); + tm16xx_set_seg(display, ds->grid, ds->segment, val); + } + } + + for (; i < display->num_digits; i++) { + digit =3D &display->digits[i]; + digit->value =3D 0; + for (j =3D 0; j < TM16XX_DIGIT_SEGMENTS; j++) { + ds =3D &digit->segments[j]; + tm16xx_set_seg(display, ds->grid, ds->segment, 0); + } + } + + schedule_work(&display->flush_display); + return count; +} + +/** + * tm16xx_num_digits_show() - Sysfs: show number of digits on display + * @dev: pointer to device + * @attr: device attribute (unused) + * @buf: output buffer + * + * Return: number of bytes written + */ +static ssize_t tm16xx_num_digits_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev =3D dev_get_drvdata(dev); + struct tm16xx_display *display =3D dev_get_drvdata(led_cdev->dev->parent); + + return sprintf(buf, "%u\n", display->num_digits); +} + +/** + * tm16xx_map_seg7_show() - Sysfs: show current 7-segment character map (b= inary blob) + * @dev: pointer to device + * @attr: device attribute (unused) + * @buf: output buffer + * + * Return: size of map_seg7 + */ +static ssize_t tm16xx_map_seg7_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + memcpy(buf, &map_seg7, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +/** + * tm16xx_map_seg7_store() - Sysfs: set 7-segment character map (binary bl= ob) + * @dev: pointer to device + * @attr: device attribute (unused) + * @buf: new mapping (must match size of map_seg7) + * @cnt: buffer length + * + * Return: cnt on success, negative errno on failure + */ +static ssize_t tm16xx_map_seg7_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t cnt) +{ + if (cnt !=3D sizeof(map_seg7)) + return -EINVAL; + memcpy(&map_seg7, buf, cnt); + return cnt; +} + +static DEVICE_ATTR(value, 0644, tm16xx_value_show, tm16xx_value_store); +static DEVICE_ATTR(num_digits, 0444, tm16xx_num_digits_show, NULL); +static DEVICE_ATTR(map_seg7, 0644, tm16xx_map_seg7_show, tm16xx_map_seg7_s= tore); + +static struct attribute *tm16xx_main_led_attrs[] =3D { + &dev_attr_value.attr, + &dev_attr_num_digits.attr, + &dev_attr_map_seg7.attr, + NULL, +}; +ATTRIBUTE_GROUPS(tm16xx_main_led); + +/** + * tm16xx_display_init() - Initialize display hardware and state + * @display: pointer to tm16xx_display + * + * Return: 0 on success, negative error code on failure + */ +static int tm16xx_display_init(struct tm16xx_display *display) +{ + unsigned int nbits =3D tm16xx_led_nbits(display); + + dev_dbg(display->dev, "Initializing display\n"); + schedule_work(&display->flush_init); + flush_work(&display->flush_init); + if (display->flush_status < 0) + return display->flush_status; + + if (tm16xx_init_value) { + tm16xx_value_store(display->main_led.dev, NULL, + tm16xx_init_value, + strlen(tm16xx_init_value)); + } else { + bitmap_fill(display->state, nbits); + schedule_work(&display->flush_display); + flush_work(&display->flush_display); + bitmap_zero(display->state, nbits); + if (display->flush_status < 0) + return display->flush_status; + } + + dev_info(display->dev, "Display turned on\n"); + + return 0; +} + +/** + * tm16xx_parse_dt() - Parse device tree for digit and LED mapping + * @dev: pointer to struct device + * @display: pointer to tm16xx_display + * + * Return: 0 on success, negative error code on failure + */ +static int tm16xx_parse_dt(struct device *dev, struct tm16xx_display *disp= lay) +{ + struct fwnode_handle *leds_node, *digits_node, *child; + struct tm16xx_led *led; + struct tm16xx_digit *digit; + int max_grid =3D 0, max_segment =3D 0; + int ret, i, j; + u32 segments[TM16XX_DIGIT_SEGMENTS * 2]; + u32 reg[2]; + + /* parse digits */ + digits_node =3D device_get_named_child_node(dev, "digits"); + if (digits_node) { + display->num_digits =3D 0; + fwnode_for_each_child_node(digits_node, child) + display->num_digits++; + + dev_dbg(dev, "Number of digits: %u\n", display->num_digits); + + if (display->num_digits) { + display->digits =3D devm_kcalloc(dev, display->num_digits, + sizeof(*display->digits), + GFP_KERNEL); + if (!display->digits) { + fwnode_handle_put(digits_node); + return -ENOMEM; + } + + i =3D 0; + fwnode_for_each_child_node(digits_node, child) { + digit =3D &display->digits[i]; + + ret =3D fwnode_property_read_u32(child, "reg", + reg); + if (ret < 0) { + fwnode_handle_put(child); + fwnode_handle_put(digits_node); + return ret; + } + + ret =3D fwnode_property_read_u32_array(child, + "segments", + segments, + TM16XX_DIGIT_SEGMENTS * 2); + if (ret < 0) { + fwnode_handle_put(child); + fwnode_handle_put(digits_node); + return ret; + } + + for (j =3D 0; j < TM16XX_DIGIT_SEGMENTS; ++j) { + digit->segments[j].grid =3D segments[2 * j]; + digit->segments[j].segment =3D segments[2 * j + 1]; + max_grid =3D umax(max_grid, + digit->segments[j].grid); + max_segment =3D umax(max_segment, + digit->segments[j].segment); + } + digit->value =3D 0; + i++; + } + + fwnode_handle_put(digits_node); + } + } + + /* parse leds */ + leds_node =3D device_get_named_child_node(dev, "leds"); + if (leds_node) { + display->num_leds =3D 0; + fwnode_for_each_child_node(leds_node, child) + display->num_leds++; + + dev_dbg(dev, "Number of LEDs: %u\n", display->num_leds); + + if (display->num_leds) { + display->leds =3D devm_kcalloc(dev, display->num_leds, + sizeof(*display->leds), + GFP_KERNEL); + if (!display->leds) { + fwnode_handle_put(leds_node); + return -ENOMEM; + } + + i =3D 0; + fwnode_for_each_child_node(leds_node, child) { + led =3D &display->leds[i]; + ret =3D fwnode_property_read_u32_array(child, + "reg", reg, + 2); + if (ret < 0) { + fwnode_handle_put(child); + fwnode_handle_put(leds_node); + return ret; + } + + led->grid =3D reg[0]; + led->segment =3D reg[1]; + max_grid =3D umax(max_grid, led->grid); + max_segment =3D umax(max_segment, led->segment); + i++; + } + } + + fwnode_handle_put(leds_node); + } + + if (max_grid >=3D display->controller->max_grids) { + dev_err(dev, "grid %u exceeds controller max_grids %u\n", + max_grid, display->controller->max_grids); + return -EINVAL; + } + + if (max_segment >=3D display->controller->max_segments) { + dev_err(dev, "segment %u exceeds controller max_segments %u\n", + max_segment, display->controller->max_segments); + return -EINVAL; + } + + display->num_grids =3D max_grid + 1; + display->num_segments =3D max_segment + 1; + + dev_dbg(dev, "Number of grids: %u\n", display->num_grids); + dev_dbg(dev, "Number of segments: %u\n", display->num_segments); + + return 0; +} + +/** + * tm16xx_probe() - Probe and initialize display device, register LEDs + * @display: pointer to tm16xx_display + * + * Return: 0 on success, negative error code on failure + */ +int tm16xx_probe(struct tm16xx_display *display) +{ + struct device *dev =3D display->dev; + struct led_classdev *main =3D &display->main_led; + struct fwnode_handle *leds_node, *child; + unsigned int nbits; + int ret, i; + + dev_dbg(dev, "Probing device\n"); + ret =3D tm16xx_parse_dt(dev, display); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to parse device tree\n"); + + nbits =3D tm16xx_led_nbits(display); + display->state =3D devm_bitmap_zalloc(dev, nbits, GFP_KERNEL); + if (!display->state) + return -ENOMEM; + + mutex_init(&display->lock); + INIT_WORK(&display->flush_init, tm16xx_display_flush_init); + INIT_WORK(&display->flush_display, tm16xx_display_flush_data); + + /* Initialize main LED properties */ + if (dev->of_node) + main->name =3D dev->of_node->name; + if (!main->name) + main->name =3D "display"; + device_property_read_string(dev, "label", &main->name); + + main->max_brightness =3D display->controller->max_brightness; + device_property_read_u32(dev, "max-brightness", &main->max_brightness); + main->max_brightness =3D umin(main->max_brightness, + display->controller->max_brightness); + + main->brightness =3D main->max_brightness; + device_property_read_u32(dev, "default-brightness", &main->brightness); + main->brightness =3D umin(main->brightness, main->max_brightness); + + main->brightness_set =3D tm16xx_brightness_set; + main->groups =3D tm16xx_main_led_groups; + main->flags =3D LED_RETAIN_AT_SHUTDOWN | LED_CORE_SUSPENDRESUME; + + ret =3D led_classdev_register(dev, main); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to register main LED\n"); + + i =3D 0; + leds_node =3D device_get_named_child_node(dev, "leds"); + fwnode_for_each_child_node(leds_node, child) { + struct tm16xx_led *led =3D &display->leds[i]; + struct led_init_data led_init =3D { + .fwnode =3D child, + .devicename =3D dev_name(main->dev), + .devname_mandatory =3D true, + .default_label =3D "led", + }; + led->cdev.max_brightness =3D 1; + led->cdev.brightness_set =3D tm16xx_led_set; + led->cdev.flags =3D LED_RETAIN_AT_SHUTDOWN | + LED_CORE_SUSPENDRESUME; + + ret =3D led_classdev_register_ext(dev, &led->cdev, &led_init); + if (ret < 0) { + fwnode_handle_put(child); + dev_err_probe(dev, ret, "Failed to register LED %s\n", + led->cdev.name); + goto unregister_leds; + } + + i++; + } + + ret =3D tm16xx_display_init(display); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to initialize display\n"); + goto unregister_leds; + } + + return 0; + +unregister_leds: + while (i--) + led_classdev_unregister(&display->leds[i].cdev); + + led_classdev_unregister(main); + return ret; +} +EXPORT_SYMBOL_NS(tm16xx_probe, "TM16XX"); + +/** + * tm16xx_remove() - Remove display, unregister LEDs, blank output + * @display: pointer to tm16xx_display + */ +void tm16xx_remove(struct tm16xx_display *display) +{ + unsigned int nbits =3D tm16xx_led_nbits(display); + struct tm16xx_led *led; + + dev_dbg(display->dev, "Removing display\n"); + + /* + * Unregister LEDs first to immediately stop trigger activity. + * This prevents LED triggers from attempting to access hardware + * after it's been disconnected or driver unloaded. + */ + for (int i =3D 0; i < display->num_leds; i++) { + led =3D &display->leds[i]; + led_classdev_unregister(&led->cdev); + } + led_classdev_unregister(&display->main_led); + + /* Clear display state */ + bitmap_zero(display->state, nbits); + schedule_work(&display->flush_display); + flush_work(&display->flush_display); + + /* Turn off display */ + display->main_led.brightness =3D LED_OFF; + schedule_work(&display->flush_init); + flush_work(&display->flush_init); + + dev_info(display->dev, "Display turned off\n"); +} +EXPORT_SYMBOL_NS(tm16xx_remove, "TM16XX"); + +MODULE_AUTHOR("Jean-Fran=C3=A7ois Lessard"); +MODULE_DESCRIPTION("TM16xx LED Display Controllers"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Fri Oct 3 20:53:47 2025 Received: from mail-qk1-f182.google.com (mail-qk1-f182.google.com [209.85.222.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 36E8625B2FA; Mon, 25 Aug 2025 03:32:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092769; cv=none; b=EBDHmGBAWATx88+6E9ruJ4xllbw6HgpCseG1E5ETNziTH3iouQ/rG6k7GJgZkIt6RGhGpM2ngZwRfNr/9HOGm06S74nJ5CVJsz39dcxSus3xQ0Ki2ecKDn4TfdW2h3gaTPaTefsIDPS3hOhWnlTbPfuxSy8il477kzfqMbvtVtQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092769; c=relaxed/simple; bh=nfrC7Dq5iVIZ4wpzGKE83dY5A6KA6AGe9Beib6kpN4s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=loPald3Xz45t85da8h8P/kyKP0AZQCTS7AnzORzAMcs5NWSRoLEM5FgAbbUfsCV7RaAuMdiyZoLiKv+EqJNniGZaRxBx1JSgP3DmuLWObUSXSzyCb2BQVg5pXRXv001mwwpcq1Q88/Yk1l8KpiJyIKCEO8TANQ2xkSl0kis1s8U= 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=hCVDN/ec; arc=none smtp.client-ip=209.85.222.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="hCVDN/ec" Received: by mail-qk1-f182.google.com with SMTP id af79cd13be357-7e87050b077so639745385a.1; Sun, 24 Aug 2025 20:32:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756092765; x=1756697565; 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=MIKdJcuaWwCgmuK0P7/LNXcVQnE6Zoh4q8hGJRCbBdw=; b=hCVDN/ecDz92uYSv7t/xCY5Ayr1L6mu8Xa4/nz35I5fz7DvLmA7JCcCLNKm44fF+dM LJiwaZjW3LG+zoms9VYcYZF/idnwcBoEE1U2jG8grhPi6UMJ94VfTfrS5q5/OI9IOp12 GkEffCwvOvkuoAmDck2KxXcUpEnAb8CXJKgAe/kyS7Q70BQysu1MvyWfVz3x28wRu70Q eFS6wqzHEIF3ruTUjhKBhJWSuKNShmrYXSiYNjXdA3G++/X7KOx3xwPm7Ha7BLu8iqP3 /UpkdCEyJj8TiBZb2S3swJbtV4cdHBGkir1JnbK68cpA8c1f0EweReGS4v4NtkADnD/s jtPA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756092765; x=1756697565; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=MIKdJcuaWwCgmuK0P7/LNXcVQnE6Zoh4q8hGJRCbBdw=; b=tH3BxThmjdjDY8r+RsFMFlVLgXC2Syx9kw6r4oGDneWtSEiqH2HoPIZ7gFo9e8KcPQ 0fOhGSxFW9bK63VEp+85itrjlrG7y3l9oMEBmwOdlMNyOHSLngBKOTZ1oED0fOuyipSz Iz8zcN3C4r5Q6E5UpB+/7i3blSG9GYy+RgF3++ZqAi1wiGi1azh3WFx/LFgDjH1vWtY8 U+d0LIknuZEokutyZ3nUJ21M5C1Oi7XuRcBcc/u1Hg+pRGyEpsQreSWYGvfH+paPLfeH uwS956ftQkGbYXZgndWWiqNwOeN+dfTo7H/R/ACrxuxciszJp8taSexuQcX7agogdw50 MkDw== X-Forwarded-Encrypted: i=1; AJvYcCUqi94X3ozN4i8Lpwc8fqyEddFZ83ZAKZMTtvTU0wTOksqTOxawPJ+hqcQyYtMiSFiRjLy9mVKp6qwZgA==@vger.kernel.org, AJvYcCWjaoOL4HzayR6ihEDLCMu4PVKHwjeYWoy6DLMcf/0N2gBL5nw4VKBzJRX8A26RsUGevWm3QReDIS4m@vger.kernel.org X-Gm-Message-State: AOJu0YwI5tHRHqDi6YX24l0hti3n+ufwLlLk6XX/65eVQVDbZAPM6LSv le89jSW/yR5DppzV+AseP07Ng35wkF0YyhFgeQhdKTr4Vg3PZlMS6XVhqg4G1rNR X-Gm-Gg: ASbGncuzLhrgvVQmnwcTpgnc5TEd6oveAYozj2Eiz9Zjeswuf49tgSLAnFJnBnI6k4q pBXEJayRe9Bv3Wrf6zsEvcwzgMZD3cuo1/tNn5Ix5+J2BroP8Rh94mtMLCSY0EHysZNJAziX87k DHk5oND4IXeJEyOGFK5W2AuI5ystL6bB6I0omTY5ozIEtwedqPCO9MjGxpChEwnYelbWOfuKeUo BbTTVeijFqm8CFsn+8LL1lo4pYw/mMQU3NjxG2c12mCh/RPNDETqEimw+JCuYiWccAMo93nftuF BGwulIZRr/T+nMHBL1c3e4yfEPAGM8hzgHuLLAns7M1d0ytYlx9ZUx7P9+wEWa9nZX+MHRGXFRp 9/MPLhh9zo7iszAtTr2zhqZ7g2Jq4jDl3680eZ5NKJIYkj+dlHoePSf3snhffUMuaHCXH X-Google-Smtp-Source: AGHT+IEHX49CCp+KtvubLICzgkECfpLuUQlpUPZMAefXMJh3H91ibhW9hvlFju9kWlgy2RP/AtFsCw== X-Received: by 2002:a05:620a:4542:b0:7e9:f1f2:12d3 with SMTP id af79cd13be357-7ea10f94e0dmr1254199085a.13.1756092764791; Sun, 24 Aug 2025 20:32:44 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id af79cd13be357-7ebf2b4f01fsm398761785a.36.2025.08.24.20.32.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 24 Aug 2025 20:32:44 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Geert Uytterhoeven , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH v4 4/6] auxdisplay: TM16xx: Add keypad support for scanning matrix keys Date: Sun, 24 Aug 2025 23:32:30 -0400 Message-ID: <20250825033237.60143-5-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com> References: <20250825033237.60143-1-jefflessard3@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 Add support for keypad scanning on TM16xx-compatible auxiliary display controllers. It handles keypad initialization, scanning, and input reporting for matrix keys managed by the TM16xx devices. Key features include: - Input device registration configured by device properties (poll-interval, linux,keymap, autorepeat) - Key state tracking using managed bitmaps - Matrix scanning and event reporting integrated with Linux input subsystem This code is separated from main core driver to improve maintainability and reviewability. Signed-off-by: Jean-Fran=C3=A7ois Lessard --- Notes: checkpatch reports false positives that are intentionally ignored: COMPLEX_MACRO/MACRO_ARG_REUSE for tm16xx_for_each_key(): This is a standard iterator pattern following kernel conventions (similar to for_each_* macros throughout the kernel). The nested for loops are the correct implementation for matrix iteration. MAINTAINERS | 1 + drivers/auxdisplay/Kconfig | 11 +- drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/tm16xx.h | 27 ++++ drivers/auxdisplay/tm16xx_core.c | 4 + drivers/auxdisplay/tm16xx_keypad.c | 208 +++++++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 drivers/auxdisplay/tm16xx_keypad.c diff --git a/MAINTAINERS b/MAINTAINERS index 0ed971881..8edcdd52c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25409,6 +25409,7 @@ F: Documentation/ABI/testing/sysfs-class-leds-tm16xx F: Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml F: drivers/auxdisplay/tm16xx.h F: drivers/auxdisplay/tm16xx_core.c +F: drivers/auxdisplay/tm16xx_keypad.c =20 TMIO/SDHI MMC DRIVER M: Wolfram Sang diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 7b58c6cc8..b5dcd024d 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -527,11 +527,14 @@ config SEG_LED_GPIO will be called seg-led-gpio. =20 config TM16XX - tristate "TM16XX-compatible 7-segment LED controllers" + tristate "TM16XX-compatible 7-segment LED controllers with keyscan" depends on SPI || I2C + depends on INPUT select NEW_LEDS select LEDS_CLASS select LEDS_TRIGGERS + select INPUT_MATRIXKMAP + select TM16XX_KEYPAD if (INPUT) help This driver supports the following TM16XX compatible I2C and SPI 7-segment led display chips: @@ -544,6 +547,12 @@ config TM16XX This driver can also be built as a module. If so, the module will be called tm16xx. =20 +config TM16XX_KEYPAD + bool + depends on TM16XX + help + Enable keyscan support for TM16XX driver. + # # Character LCD with non-conforming interface section # diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index 7ecf3cd4a..a9b9c8ff0 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_PARPORT_PANEL) +=3D panel.o obj-$(CONFIG_SEG_LED_GPIO) +=3D seg-led-gpio.o obj-$(CONFIG_TM16XX) +=3D tm16xx.o tm16xx-y +=3D tm16xx_core.o +tm16xx-$(CONFIG_TM16XX_KEYPAD) +=3D tm16xx_keypad.o diff --git a/drivers/auxdisplay/tm16xx.h b/drivers/auxdisplay/tm16xx.h index a7ce483d3..65e2511cd 100644 --- a/drivers/auxdisplay/tm16xx.h +++ b/drivers/auxdisplay/tm16xx.h @@ -104,6 +104,7 @@ struct tm16xx_display; struct tm16xx_digit; struct tm16xx_led; +struct tm16xx_keypad; =20 /** * DOC: struct tm16xx_controller - Controller-specific operations and limi= ts @@ -136,6 +137,7 @@ struct tm16xx_controller { * @controller: Controller-specific function table and limits. * @client: Union of I2C and SPI client pointers. * @spi_buffer: DMA-safe buffer for SPI transactions, or NULL for I2C. + * @keypad: Opaque pointer to tm16xx_keypad struct. * @num_grids: Number of controller grids in use. * @num_segments: Number of controller segments in use. * @main_led: LED class device for the entire display. @@ -157,6 +159,7 @@ struct tm16xx_display { struct spi_device *spi; } client; u8 *spi_buffer; + struct tm16xx_keypad *keypad; u8 num_grids; u8 num_segments; struct led_classdev main_led; @@ -174,4 +177,28 @@ struct tm16xx_display { int tm16xx_probe(struct tm16xx_display *display); void tm16xx_remove(struct tm16xx_display *display); =20 +/* keypad support */ +#if IS_ENABLED(CONFIG_TM16XX_KEYPAD) +int tm16xx_keypad_probe(struct tm16xx_display *display); +void tm16xx_set_key(const struct tm16xx_display *display, const u8 row, + const u8 col, const bool pressed); +#else +static inline int tm16xx_keypad_probe(struct tm16xx_display *display) +{ + return 0; +} + +static inline void tm16xx_set_key(const struct tm16xx_display *display, + const u8 row, const u8 col, + const bool pressed) +{ +} +#endif + +#define tm16xx_for_each_key(display, _r, _c) \ + for (unsigned int _r =3D 0; \ + _r < (display)->controller->max_key_rows; _r++) \ + for (unsigned int _c =3D 0; \ + _c < (display)->controller->max_key_cols; _c++) + #endif /* _TM16XX_H */ diff --git a/drivers/auxdisplay/tm16xx_core.c b/drivers/auxdisplay/tm16xx_c= ore.c index 415be7747..e21c41a09 100644 --- a/drivers/auxdisplay/tm16xx_core.c +++ b/drivers/auxdisplay/tm16xx_core.c @@ -566,6 +566,10 @@ int tm16xx_probe(struct tm16xx_display *display) goto unregister_leds; } =20 + ret =3D tm16xx_keypad_probe(display); + if (ret < 0) + dev_warn(dev, "Failed to initialize keypad: %d\n", ret); + return 0; =20 unregister_leds: diff --git a/drivers/auxdisplay/tm16xx_keypad.c b/drivers/auxdisplay/tm16xx= _keypad.c new file mode 100644 index 000000000..391ae737e --- /dev/null +++ b/drivers/auxdisplay/tm16xx_keypad.c @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TM16xx and compatible LED display/keypad controller driver + * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips. + * + * Copyright (C) 2024 Jean-Fran=C3=A7ois Lessard + */ + +#include +#include +#include + +#include "tm16xx.h" + +/** + * struct tm16xx_keypad - Keypad matrix state and input device + * @input: Input device for reporting key events. + * @state: Current bitmap of key states. + * @last_state: Previous bitmap of key states for change detection. + * @changes: Bitmap of key state changes since last poll. + * @row_shift: Row shift for keymap encoding. + */ +struct tm16xx_keypad { + struct input_dev *input; + unsigned long *state; + unsigned long *last_state; + unsigned long *changes; + u8 row_shift; +}; + +/** + * tm16xx_key_nbits() - Number of bits for the keypad state bitmap + * @display: pointer to tm16xx_display + * + * Return: total bits in keypad state bitmap (max_key_rows * max_key_cols) + */ +static inline unsigned int tm16xx_key_nbits(const struct tm16xx_display *d= isplay) +{ + return display->controller->max_key_rows * + display->controller->max_key_cols; +} + +/** + * tm16xx_get_key_row() - Get row index from keypad bit index + * @display: pointer to tm16xx_display + * @bit: bit index in state bitmap + * + * Return: row index + */ +static inline u8 tm16xx_get_key_row(const struct tm16xx_display *display, + const unsigned int bit) +{ + return bit / display->controller->max_key_cols; +} + +/** + * tm16xx_get_key_col() - Get column index from keypad bit index + * @display: pointer to tm16xx_display + * @bit: bit index in state bitmap + * + * Return: column index + */ +static inline u8 tm16xx_get_key_col(const struct tm16xx_display *display, + const unsigned int bit) +{ + return bit % display->controller->max_key_cols; +} + +/** + * tm16xx_set_key() - Set the keypad state for a key + * @display: pointer to tm16xx_display + * @row: row index + * @col: column index + * @pressed: true if pressed, false otherwise + */ +void tm16xx_set_key(const struct tm16xx_display *display, const u8 row, + const u8 col, const bool pressed) +{ + __assign_bit(row * display->controller->max_key_cols + col, + display->keypad->state, pressed); +} +EXPORT_SYMBOL_NS(tm16xx_set_key, "TM16XX"); + +/** + * tm16xx_keypad_poll() - Polls the keypad, reports events + * @input: pointer to input_dev + * + * Reads the matrix keypad state, compares with previous state, and + * reports key events to the input subsystem. + */ +static void tm16xx_keypad_poll(struct input_dev *input) +{ + struct tm16xx_display *display =3D input_get_drvdata(input); + struct tm16xx_keypad *keypad =3D display->keypad; + const unsigned short *keycodes =3D keypad->input->keycode; + unsigned int nbits =3D tm16xx_key_nbits(display); + unsigned int bit, scancode; + u8 row, col; + bool pressed; + int ret; + + bitmap_zero(keypad->state, nbits); + bitmap_zero(keypad->changes, nbits); + + scoped_guard(mutex, &display->lock) { + ret =3D display->controller->keys(display); + } + + if (ret < 0) { + dev_err(display->dev, "Reading failed: %d\n", ret); + return; + } + + bitmap_xor(keypad->changes, keypad->state, keypad->last_state, nbits); + + for_each_set_bit(bit, keypad->changes, nbits) { + row =3D tm16xx_get_key_row(display, bit); + col =3D tm16xx_get_key_col(display, bit); + pressed =3D _test_bit(bit, keypad->state); + scancode =3D MATRIX_SCAN_CODE(row, col, keypad->row_shift); + + dev_dbg(display->dev, + "key changed: %u, row=3D%u col=3D%u down=3D%d\n", bit, row, + col, pressed); + + input_event(keypad->input, EV_MSC, MSC_SCAN, scancode); + input_report_key(keypad->input, keycodes[scancode], pressed); + } + input_sync(keypad->input); + + bitmap_copy(keypad->last_state, keypad->state, nbits); +} + +/** + * tm16xx_keypad_probe() - Initialize keypad/input device + * @display: pointer to tm16xx_display + * + * Return: 0 on success, negative error code on failure + */ +int tm16xx_keypad_probe(struct tm16xx_display *display) +{ + const u8 rows =3D display->controller->max_key_rows; + const u8 cols =3D display->controller->max_key_cols; + struct tm16xx_keypad *keypad; + struct input_dev *input; + unsigned int poll_interval, nbits; + int ret; + + if (!display->controller->keys || !rows || !cols) { + dev_dbg(display->dev, "keypad not supported\n"); + return 0; + } + + if (!device_property_present(display->dev, "poll-interval") || + !device_property_present(display->dev, "linux,keymap")) { + dev_dbg(display->dev, "keypad disabled\n"); + return 0; + } + + dev_dbg(display->dev, "Configuring keypad\n"); + + ret =3D device_property_read_u32(display->dev, "poll-interval", + &poll_interval); + if (ret < 0) + return dev_err_probe(display->dev, ret, + "Failed to read poll-interval\n"); + + keypad =3D devm_kzalloc(display->dev, sizeof(*keypad), GFP_KERNEL); + if (!keypad) + return -ENOMEM; + display->keypad =3D keypad; + + nbits =3D tm16xx_key_nbits(display); + keypad->state =3D devm_bitmap_zalloc(display->dev, nbits, GFP_KERNEL); + keypad->last_state =3D devm_bitmap_zalloc(display->dev, nbits, GFP_KERNEL= ); + keypad->changes =3D devm_bitmap_zalloc(display->dev, nbits, GFP_KERNEL); + if (!keypad->state || !keypad->last_state || !keypad->changes) + return -ENOMEM; + + input =3D devm_input_allocate_device(display->dev); + if (!input) + return -ENOMEM; + input->name =3D "tm16xx-keypad"; + keypad->input =3D input; + input_set_drvdata(input, display); + + keypad->row_shift =3D get_count_order(cols); + ret =3D matrix_keypad_build_keymap(NULL, "linux,keymap", rows, cols, NULL, + input); + if (ret < 0) + return dev_err_probe(display->dev, ret, + "Failed to build keymap\n"); + + if (device_property_read_bool(display->dev, "autorepeat")) + __set_bit(EV_REP, input->evbit); + + input_setup_polling(input, tm16xx_keypad_poll); + input_set_poll_interval(input, poll_interval); + ret =3D input_register_device(input); + if (ret < 0) + return dev_err_probe(display->dev, ret, + "Failed to register input device\n"); + + dev_dbg(display->dev, "keypad rows=3D%u, cols=3D%u, poll=3D%u\n", rows, c= ols, + poll_interval); + + return 0; +} --=20 2.43.0 From nobody Fri Oct 3 20:53:47 2025 Received: from mail-qt1-f179.google.com (mail-qt1-f179.google.com [209.85.160.179]) (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 5313025D917; Mon, 25 Aug 2025 03:32:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.179 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092769; cv=none; b=tWb0isGeNcOGub8iljTMMp4J9Wyz3Oxj8J/nvnbpRujUqsxNoX8XIFreB1hF41oPi1GuKVJ6n+k/vQHk1d8pn6HMJL3GGLfDhE/u/ofPKjeqqNzfekuTzRiD3FMB6qB8qKci9PTAPaRHD1eDSB7zEsZloMGc3n9mfwanyn2Ltv0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092769; c=relaxed/simple; bh=uQdUs+gRS29PSICgO4m/EfZCr5hAaQDUvp6ZoOPquHI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RB4qFTsGs4SGOTF4WBnYyWfv9qcTi5WPftxuBr4uOehM95j2EoKRCUjB7lpcUP83OjDbKk+lZz5otFE6C908jxYFbWLNh6VQWDXktZbddakGhGNewDqXRewP4EvHcjZ5bWspNxQu8mRzs/5HBc1GkJovenCibP7bAJ56x6Om91Y= 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=kznsV13Q; arc=none smtp.client-ip=209.85.160.179 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="kznsV13Q" Received: by mail-qt1-f179.google.com with SMTP id d75a77b69052e-4b28184a8b3so47122401cf.1; Sun, 24 Aug 2025 20:32:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756092766; x=1756697566; 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=dXf+dqUtTAWA9fwRLFryIKYUZaDkHD+b1P+hK8T7SS4=; b=kznsV13QvghyuQyRddmhSgN8WoB+CVmCCw8dA3evXICILX2AIv4V+NozBoi1/6P6hh dC5jYsSGndDBaOSxjO/W2CZlLjACHdtlNY7RUE8IOFkZTMF17535brNdDXNzZMk3TJmX 8AcHe4C6w69BgCYWLiixHLpFYFPfFFqcxepEch87BQz9J4c1DeC8Fbag/3m9GPYUQpAw YVBs52TtZN7+kxEjAGCqaK4UZ8IwkLZjnLSuDfxzSUFvjkN/GblLGyvUViWahqo0tOo7 yzPDyImKCuyNQsvBESxqsS6/7A97yrPcx4/va5GdCqUTqtAE0680k6rgqqKdM4yZmSZu 6Qdg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756092766; x=1756697566; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dXf+dqUtTAWA9fwRLFryIKYUZaDkHD+b1P+hK8T7SS4=; b=C95Efrfvh3TFKwgghgqxBwXVBBh/WCxswgdfVi0OBI+kRrxyo20ZckShXnn8edQDva +5Kvkz79M1ZRfIvMRdMVum7c2QzqoiM1GBosfaQRaHQZ/PJDWjiT/FFLRXhjBBRVh+YB 2iCNwX6qmwNa7Nb9fEWkLjeEOw/4wL+Ly/Lduto6u+vRaX5QY22koMwQif2FwF9u/p/G Qgj+n9ksew1GHkOPgafk7uIoyyoAWBnzjoI23PYP7Rx2miSuxTZVLoGP4yNWCZtbSaXX Z5LdNcZSrTED6wIPSpZ6I3UaGS1QKRz2+5TvUXv3KFSS2BeoEJG1lqCSaCW4j+RGXMAQ ngaw== X-Forwarded-Encrypted: i=1; AJvYcCU7pdmMoCCSUfAxb4bj6g4LmgNzj0meKGTDGu4c5CenbnaHAutEOwUA5OTKTaGiLKyiqHTwRP0fnWp+@vger.kernel.org, AJvYcCW9i4OMHy8XS28fiXhr1hFVYSupX6gKeQTxR9A3WqI3wYU5/kM/KiBEdRoYQLN4FszqjX9a2IiQscMIdA==@vger.kernel.org X-Gm-Message-State: AOJu0YzXUpK8ckzUbhweLoiNsBhpdwPh8bj45mHilCufLoUmO6C3FRgC gRjDr6xvb1KAsGogswCVmuR81MH5hDEF5NQiDBhtFaL1vBPD4HKpJm4hsFSeK5Zp X-Gm-Gg: ASbGncutOyF9gDF/5sJccTlsOwBhDy/sBgJ8DDMb6caRn3dIMCMXdwHy8+HwSSZtc0b we3ji5HxQkHjAOvwrVXiMo/8mK7f8FjVUzw3z59JzlH2GBeoD0dcrG5jDqg5wDfFANYR21YcBeA l3hhzhAkr5gtmaP2/RZikh98t3+NBD2t2NFo3P2lesqRqqxg/NQ/xs3RsHyUTM7K/kBFCePRxNp lWya+MUdXvaQ0Cc5voc6ASa2KPRykkYmehqNVgGskZNyqh0hLO/3Doqjtas8Mj4U7JuZA07TMaY FWxHya5bRUHiVfIpVFTSgcG7qAK99EI02ezxO75IvLnQspVU0uWOonMycspzdUjw1KDPW8ZFHqM VtAuS7hfz+DqBJu84HdwE399X0ZSQzSAaGBAowO4XInOQOIF7ZWEsMIBOajZLLIqvGinM X-Google-Smtp-Source: AGHT+IEZxMZi3ocIIlYZ8mQpbFhBd5hqZk64cDvWc/PklYP03OTTS+gXDLjTBt/IJ9m0oQC5Vvvq4A== X-Received: by 2002:ac8:5e49:0:b0:4b2:9ac2:686e with SMTP id d75a77b69052e-4b2aab4a159mr126509091cf.77.1756092766101; Sun, 24 Aug 2025 20:32:46 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-4b2b8ca1609sm43240851cf.17.2025.08.24.20.32.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 24 Aug 2025 20:32:45 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Geert Uytterhoeven , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH v4 5/6] auxdisplay: TM16xx: Add support for I2C-based controllers Date: Sun, 24 Aug 2025 23:32:31 -0400 Message-ID: <20250825033237.60143-6-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com> References: <20250825033237.60143-1-jefflessard3@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 Add support for TM16xx-compatible auxiliary display controllers connected via the I2C bus. The implementation includes: - I2C driver registration and initialization - Probe/remove logic for I2C devices - Controller-specific handling and communication sequences - Integration with the TM16xx core driver for common functionality This allows platforms using TM16xx or compatible controllers over I2C to be managed by the TM16xx driver infrastructure. Signed-off-by: Jean-Fran=C3=A7ois Lessard --- MAINTAINERS | 1 + drivers/auxdisplay/Kconfig | 7 + drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/tm16xx_i2c.c | 332 ++++++++++++++++++++++++++++++++ 4 files changed, 341 insertions(+) create mode 100644 drivers/auxdisplay/tm16xx_i2c.c diff --git a/MAINTAINERS b/MAINTAINERS index 8edcdd52c..51cc910e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25409,6 +25409,7 @@ F: Documentation/ABI/testing/sysfs-class-leds-tm16xx F: Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml F: drivers/auxdisplay/tm16xx.h F: drivers/auxdisplay/tm16xx_core.c +F: drivers/auxdisplay/tm16xx_i2c.c F: drivers/auxdisplay/tm16xx_keypad.c =20 TMIO/SDHI MMC DRIVER diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index b5dcd024d..6238e753d 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -535,6 +535,7 @@ config TM16XX select LEDS_TRIGGERS select INPUT_MATRIXKMAP select TM16XX_KEYPAD if (INPUT) + select TM16XX_I2C if (I2C) help This driver supports the following TM16XX compatible I2C and SPI 7-segment led display chips: @@ -553,6 +554,12 @@ config TM16XX_KEYPAD help Enable keyscan support for TM16XX driver. =20 +config TM16XX_I2C + tristate + depends on TM16XX + help + Enable I2C support for TM16XX driver. + # # Character LCD with non-conforming interface section # diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index a9b9c8ff0..ba7b310f5 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_SEG_LED_GPIO) +=3D seg-led-gpio.o obj-$(CONFIG_TM16XX) +=3D tm16xx.o tm16xx-y +=3D tm16xx_core.o tm16xx-$(CONFIG_TM16XX_KEYPAD) +=3D tm16xx_keypad.o +obj-$(CONFIG_TM16XX_I2C) +=3D tm16xx_i2c.o diff --git a/drivers/auxdisplay/tm16xx_i2c.c b/drivers/auxdisplay/tm16xx_i2= c.c new file mode 100644 index 000000000..3beca7e43 --- /dev/null +++ b/drivers/auxdisplay/tm16xx_i2c.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TM16xx and compatible LED display/keypad controller driver + * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips. + * + * Copyright (C) 2024 Jean-Fran=C3=A7ois Lessard + */ + +#include +#include + +#include "tm16xx.h" + +static int tm16xx_i2c_probe(struct i2c_client *client) +{ + const struct tm16xx_controller *controller; + struct tm16xx_display *display; + int ret; + + controller =3D i2c_get_match_data(client); + if (!controller) + return -EINVAL; + + display =3D devm_kzalloc(&client->dev, sizeof(*display), GFP_KERNEL); + if (!display) + return -ENOMEM; + + display->client.i2c =3D client; + display->dev =3D &client->dev; + display->controller =3D controller; + + i2c_set_clientdata(client, display); + + ret =3D tm16xx_probe(display); + if (ret) + return ret; + + return 0; +} + +static void tm16xx_i2c_remove(struct i2c_client *client) +{ + struct tm16xx_display *display =3D i2c_get_clientdata(client); + + tm16xx_remove(display); +} + +/** + * tm16xx_i2c_write() - I2C write helper for controller + * @display: pointer to tm16xx_display structure + * @data: command and data bytes to send + * @len: number of bytes in @data + * + * Return: 0 on success, negative error code on failure + */ +static int tm16xx_i2c_write(struct tm16xx_display *display, u8 *data, size= _t len) +{ + dev_dbg(display->dev, "i2c_write %*ph", (char)len, data); + + /* expected sequence: S Command [A] Data [A] P */ + struct i2c_msg msg =3D { + .addr =3D data[0] >> 1, + .flags =3D 0, + .len =3D len - 1, + .buf =3D &data[1], + }; + int ret; + + ret =3D i2c_transfer(display->client.i2c->adapter, &msg, 1); + if (ret < 0) + return ret; + + return (ret =3D=3D 1) ? 0 : -EIO; +} + +/** + * tm16xx_i2c_read() - I2C read helper for controller + * @display: pointer to tm16xx_display structure + * @cmd: command/address byte to send before reading + * @data: buffer to receive data + * @len: number of bytes to read into @data + * + * Return: 0 on success, negative error code on failure + */ +static int tm16xx_i2c_read(struct tm16xx_display *display, u8 cmd, u8 *dat= a, + size_t len) +{ + /* expected sequence: S Command [A] [Data] [A] P */ + struct i2c_msg msgs[1] =3D {{ + .addr =3D cmd >> 1, + .flags =3D I2C_M_RD | I2C_M_NO_RD_ACK, + .len =3D len, + .buf =3D data, + }}; + int ret; + + ret =3D i2c_transfer(display->client.i2c->adapter, msgs, ARRAY_SIZE(msgs)= ); + if (ret < 0) + return ret; + + dev_dbg(display->dev, "i2c_read %ph: %*ph\n", &cmd, (char)len, data); + + return (ret =3D=3D ARRAY_SIZE(msgs)) ? 0 : -EIO; +} + +/* I2C controller-specific functions */ +static int tm1650_init(struct tm16xx_display *display) +{ + u8 cmds[2]; + const enum led_brightness brightness =3D display->main_led.brightness; + + cmds[0] =3D TM1650_CMD_CTRL; + cmds[1] =3D TM16XX_CTRL_BRIGHTNESS(brightness, brightness, TM1650) | + TM1650_CTRL_SEG8_MODE; + + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int tm1650_data(struct tm16xx_display *display, u8 index, + unsigned int grid) +{ + u8 cmds[2]; + + cmds[0] =3D TM1650_CMD_ADDR + index * 2; + cmds[1] =3D grid; /* SEG 1 to 8 */ + + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int tm1650_keys(struct tm16xx_display *display) +{ + u8 keycode, row, col; + bool pressed; + int ret; + + ret =3D tm16xx_i2c_read(display, TM1650_CMD_READ, &keycode, 1); + if (ret) + return ret; + + if (keycode =3D=3D 0x00 || keycode =3D=3D 0xFF) + return -EINVAL; + + row =3D FIELD_GET(TM1650_KEY_ROW_MASK, keycode); + pressed =3D FIELD_GET(TM1650_KEY_DOWN_MASK, keycode) !=3D 0; + if ((keycode & TM1650_KEY_COMBINED) =3D=3D TM1650_KEY_COMBINED) { + tm16xx_set_key(display, row, 0, pressed); + tm16xx_set_key(display, row, 1, pressed); + } else { + col =3D FIELD_GET(TM1650_KEY_COL_MASK, keycode); + tm16xx_set_key(display, row, col, pressed); + } + + return 0; +} + +static int fd655_init(struct tm16xx_display *display) +{ + u8 cmds[2]; + const enum led_brightness brightness =3D display->main_led.brightness; + + cmds[0] =3D FD655_CMD_CTRL; + cmds[1] =3D TM16XX_CTRL_BRIGHTNESS(brightness, brightness % 3, FD655); + + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int fd655_data(struct tm16xx_display *display, u8 index, + unsigned int grid) +{ + u8 cmds[2]; + + cmds[0] =3D FD655_CMD_ADDR + index * 2; + cmds[1] =3D grid; /* SEG 1 to 8 */ + + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int fd6551_init(struct tm16xx_display *display) +{ + u8 cmds[2]; + const enum led_brightness brightness =3D display->main_led.brightness; + + cmds[0] =3D FD6551_CMD_CTRL; + cmds[1] =3D TM16XX_CTRL_BRIGHTNESS(brightness, ~(brightness - 1), FD6551); + + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static void hbs658_swap_nibbles(u8 *data, size_t len) +{ + for (size_t i =3D 0; i < len; i++) + data[i] =3D (data[i] << 4) | (data[i] >> 4); +} + +static int hbs658_init(struct tm16xx_display *display) +{ + const enum led_brightness brightness =3D display->main_led.brightness; + u8 cmd; + int ret; + + /* Set data command */ + cmd =3D TM16XX_CMD_WRITE | TM16XX_DATA_ADDR_AUTO; + hbs658_swap_nibbles(&cmd, 1); + ret =3D tm16xx_i2c_write(display, &cmd, 1); + if (ret < 0) + return ret; + + /* Set control command with brightness */ + cmd =3D TM16XX_CMD_CTRL | + TM16XX_CTRL_BRIGHTNESS(brightness, brightness - 1, TM16XX); + hbs658_swap_nibbles(&cmd, 1); + ret =3D tm16xx_i2c_write(display, &cmd, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int hbs658_data(struct tm16xx_display *display, u8 index, + unsigned int grid) +{ + u8 cmds[2]; + + cmds[0] =3D TM16XX_CMD_ADDR + index * 2; + cmds[1] =3D grid; + + hbs658_swap_nibbles(cmds, ARRAY_SIZE(cmds)); + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int hbs658_keys(struct tm16xx_display *display) +{ + u8 cmd, keycode, col; + int ret; + + cmd =3D TM16XX_CMD_READ; + hbs658_swap_nibbles(&cmd, 1); + ret =3D tm16xx_i2c_read(display, cmd, &keycode, 1); + if (ret) + return ret; + + hbs658_swap_nibbles(&keycode, 1); + + if (keycode !=3D 0xFF) { + col =3D FIELD_GET(HBS658_KEY_COL_MASK, keycode); + tm16xx_set_key(display, 0, col, true); + } + + return 0; +} + +/* I2C controller definitions */ +static const struct tm16xx_controller tm1650_controller =3D { + .max_grids =3D 4, + .max_segments =3D 8, + .max_brightness =3D 8, + .max_key_rows =3D 4, + .max_key_cols =3D 7, + .init =3D tm1650_init, + .data =3D tm1650_data, + .keys =3D tm1650_keys, +}; + +static const struct tm16xx_controller fd655_controller =3D { + .max_grids =3D 5, + .max_segments =3D 7, + .max_brightness =3D 3, + .max_key_rows =3D 5, + .max_key_cols =3D 7, + .init =3D fd655_init, + .data =3D fd655_data, + .keys =3D tm1650_keys, +}; + +static const struct tm16xx_controller fd6551_controller =3D { + .max_grids =3D 5, + .max_segments =3D 7, + .max_brightness =3D 8, + .max_key_rows =3D 0, + .max_key_cols =3D 0, + .init =3D fd6551_init, + .data =3D fd655_data, + .keys =3D NULL, +}; + +static const struct tm16xx_controller hbs658_controller =3D { + .max_grids =3D 5, + .max_segments =3D 8, + .max_brightness =3D 8, + .max_key_rows =3D 1, + .max_key_cols =3D 8, + .init =3D hbs658_init, + .data =3D hbs658_data, + .keys =3D hbs658_keys, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tm16xx_i2c_of_match[] =3D { + { .compatible =3D "titanmec,tm1650", .data =3D &tm1650_controller }, + { .compatible =3D "fdhisi,fd6551", .data =3D &fd6551_controller }, + { .compatible =3D "fdhisi,fd655", .data =3D &fd655_controller }, + { .compatible =3D "winrise,hbs658", .data =3D &hbs658_controller }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tm16xx_i2c_of_match); +#endif + +static const struct i2c_device_id tm16xx_i2c_id[] =3D { + { "tm1650", (kernel_ulong_t)&tm1650_controller }, + { "fd6551", (kernel_ulong_t)&fd6551_controller }, + { "fd655", (kernel_ulong_t)&fd655_controller }, + { "hbs658", (kernel_ulong_t)&hbs658_controller }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, tm16xx_i2c_id); + +static struct i2c_driver tm16xx_i2c_driver =3D { + .driver =3D { + .name =3D "tm16xx-i2c", + .of_match_table =3D of_match_ptr(tm16xx_i2c_of_match), + }, + .probe =3D tm16xx_i2c_probe, + .remove =3D tm16xx_i2c_remove, + .shutdown =3D tm16xx_i2c_remove, + .id_table =3D tm16xx_i2c_id, +}; +module_i2c_driver(tm16xx_i2c_driver); + +MODULE_AUTHOR("Jean-Fran=C3=A7ois Lessard"); +MODULE_DESCRIPTION("TM16xx-i2c LED Display Controllers"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("TM16XX"); --=20 2.43.0 From nobody Fri Oct 3 20:53:47 2025 Received: from mail-qv1-f51.google.com (mail-qv1-f51.google.com [209.85.219.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B8BB5269AEE; Mon, 25 Aug 2025 03:32:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.51 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092770; cv=none; b=dU11q8F0wmGHDKQ+hSVPeZz9tZj/JLe3Iojv1VZ2IYcD26aU4OKBiiM2iFx0cjBpzL8WRhOKjK8Y5ueqe61GdDOatz6l5+M2AO883/Ujw927ua4tTpHHyZ6WQyzkEYan4yyPqOW2nHcvVmA4XGaYRU4A84FHXsjHYU1xdAc049Y= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756092770; c=relaxed/simple; bh=86TleQdUi+JMz+iUg6eiKigh7Qwwees0tjmyxXdZMBA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=bpQI0tluVkQRyn6f5czGHbU6lMQUB6/uuQ1VX1eHSs3F6pB1bKj0zTHtkaQwxWUKtMucq8MJPqOHHvatS8hGP4aP3SPmOrNijuTZAilgSpWXvEmjUckpy6pOjyY21XFWs413H3tQEtTYsX+Ycckjbwg8uAAA+dlMM5TyTCuyiuQ= 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=Jk6PxHK+; arc=none smtp.client-ip=209.85.219.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="Jk6PxHK+" Received: by mail-qv1-f51.google.com with SMTP id 6a1803df08f44-70d93f5799bso27264336d6.1; Sun, 24 Aug 2025 20:32:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1756092768; x=1756697568; 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=TBg2VgzadmcpGOk4rK6TKexSA/3pGGW3w4j/3ZDiVuk=; b=Jk6PxHK+0SVKQBSqD1/Jg6YHkeYYnOSvmb8mMA11jhSICMqfAvmZEfji2kXF4YKuOD R+cpC6GMSpsonqY2Ko0AmVohpVoqJnSNlxjucGkpPJKcukvnznM9GL1ra84nPjnAGvZo EQbOfGdGJAyS+2uCcwJTTs9L1WZk0ECi4/58yGB2PdyabBkbL+JHdGqHZbM05VcKW8Rz hn8jEJ3iVrO8HBRe/zX9kyHBucA1LDGYYNsYZ9BjR1xsHnDbofdMUljW3yC90bswuWKN r/ObRrN8z7y76p+BZSGZPu+O2sG9cdz6qGHVgkrwmJ1C3+eQqbdChW1g4xqelJtmNUBs 42kQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1756092768; x=1756697568; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=TBg2VgzadmcpGOk4rK6TKexSA/3pGGW3w4j/3ZDiVuk=; b=YRCeqRTv4v5kajKXLD0jbTWUBrV378ZTaL6KCoTKGbSWFS/rqKvR6kownHTZnXYC2T ZUfNjkezZPDg/mnnLr6dNvmaXXQOohwhbVarsYpy3EnRCjTvajqqEFqbFUIH90E0lwe4 IF9uNH23S0kYAZdrskU4qf98Q3xD4l3a3fiogGo/6AbAKIKuwx5pkHVnSJIgKeln8wEK gAmHqEg2JDKRZjTyN84IhZazq8EzzGW91IjiHL0gBtAKT6Al+P8aOzlIH9r+eCAwU2/x lmaKhwXA+cWi9Cm8pPceqpy1eZEaWm5H9t2PB+o/jAseTcCqetzLvlb3RwSopxhf2/Q8 aiaA== X-Forwarded-Encrypted: i=1; AJvYcCWGjeySqjN3q7X1Nogbd+hM6fGJSLYowMc6RQyC5ncx3WKy00AKLwhmTxHNBtdyztLrlhGQhEaBriGxFg==@vger.kernel.org, AJvYcCXxFN+82I57LPXCnEgRUIMjsCVevs62eMFU8Tih6qOd9W3osveEDCAtfXKNTp32/HbuMmSWpJm3kJWT@vger.kernel.org X-Gm-Message-State: AOJu0YxnPk6jKReR8+HzLHQQf+JuY/rY1Rj5bhLa9guyVms+/SnwmARX tI8+osFkBmgDxuci9Brcetal45BJ4cW8xXryTfzwknIBTTbLi7yJiGredE4+fq87 X-Gm-Gg: ASbGnct3DheESHFpf3qPE8Sa4ZdhuC8iUTxz6w1UXY/XpUnicdGvBu8H1jqHMn6eiuL V3rCzhzK7KIksBDIpUsJbF9YZ75ANpJZpZHHuQG7Zv4F43HwRxGHkmUlzflp8pC/2aJMIr71UR0 pj8WEiAKwRH+X+VcPajBQUHyfeMtG5Hyx2OwjI3eLH0pR2sutACsisU2unquZ+x3ibX+lzUFsl1 03XNUyoCx1AIzqJ6Q66HqftMNr6UObGazv9IGBx0ZD/VR7iTSEX9HvOeOOsiRQR0KNSalGjKgg5 WKXdbJDu5qRV1ZnkXYEB37rOCsFLv35Vtb0PgBLmDPFepL9Es2TSHU4DpcZ8OkGZWsBdOO7dVhM GAqoaY/MknWS02L4Ign9jfKSVTDIt9JIge2JOgAqfFIdQTjx8OTrC6CGNH+0ap2TCgcNu X-Google-Smtp-Source: AGHT+IFhNLcyNRLc6FBOHTmkWtIT2E6LZrw6Ey2M5g78X5SqbCh1vY56jLnRhBMGDZid4Nt7qMWdQw== X-Received: by 2002:a05:6214:2265:b0:70d:bdd2:7cbb with SMTP id 6a1803df08f44-70dbdd285c3mr24890816d6.42.1756092767433; Sun, 24 Aug 2025 20:32:47 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-70da72dbf82sm37709216d6.70.2025.08.24.20.32.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 24 Aug 2025 20:32:47 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Geert Uytterhoeven , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: linux-kernel@vger.kernel.org, linux-leds@vger.kernel.org, devicetree@vger.kernel.org Subject: [PATCH v4 6/6] auxdisplay: TM16xx: Add support for SPI-based controllers Date: Sun, 24 Aug 2025 23:32:32 -0400 Message-ID: <20250825033237.60143-7-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250825033237.60143-1-jefflessard3@gmail.com> References: <20250825033237.60143-1-jefflessard3@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 Add support for TM16xx-compatible auxiliary display controllers connected via the SPI bus. The implementation includes: - SPI driver registration and initialization - Probe/remove logic for SPI devices - Controller-specific handling and communication sequences - Integration with the TM16xx core driver for common functionality This allows platforms using TM16xx or compatible controllers over SPI to be managed by the TM16xx driver infrastructure. Signed-off-by: Jean-Fran=C3=A7ois Lessard --- Notes: include is required for of_match_ptr MAINTAINERS | 1 + drivers/auxdisplay/Kconfig | 7 + drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/tm16xx_spi.c | 398 ++++++++++++++++++++++++++++++++ 4 files changed, 407 insertions(+) create mode 100644 drivers/auxdisplay/tm16xx_spi.c diff --git a/MAINTAINERS b/MAINTAINERS index 51cc910e2..1ee45be14 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25411,6 +25411,7 @@ F: drivers/auxdisplay/tm16xx.h F: drivers/auxdisplay/tm16xx_core.c F: drivers/auxdisplay/tm16xx_i2c.c F: drivers/auxdisplay/tm16xx_keypad.c +F: drivers/auxdisplay/tm16xx_spi.c =20 TMIO/SDHI MMC DRIVER M: Wolfram Sang diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index 6238e753d..868625596 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -536,6 +536,7 @@ config TM16XX select INPUT_MATRIXKMAP select TM16XX_KEYPAD if (INPUT) select TM16XX_I2C if (I2C) + select TM16XX_SPI if (SPI) help This driver supports the following TM16XX compatible I2C and SPI 7-segment led display chips: @@ -560,6 +561,12 @@ config TM16XX_I2C help Enable I2C support for TM16XX driver. =20 +config TM16XX_SPI + tristate + depends on TM16XX + help + Enable SPI support for TM16XX driver. + # # Character LCD with non-conforming interface section # diff --git a/drivers/auxdisplay/Makefile b/drivers/auxdisplay/Makefile index ba7b310f5..2485a3a67 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -20,3 +20,4 @@ obj-$(CONFIG_TM16XX) +=3D tm16xx.o tm16xx-y +=3D tm16xx_core.o tm16xx-$(CONFIG_TM16XX_KEYPAD) +=3D tm16xx_keypad.o obj-$(CONFIG_TM16XX_I2C) +=3D tm16xx_i2c.o +obj-$(CONFIG_TM16XX_SPI) +=3D tm16xx_spi.o diff --git a/drivers/auxdisplay/tm16xx_spi.c b/drivers/auxdisplay/tm16xx_sp= i.c new file mode 100644 index 000000000..fcc69c5f6 --- /dev/null +++ b/drivers/auxdisplay/tm16xx_spi.c @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * TM16xx and compatible LED display/keypad controller driver + * Supports TM16xx, FD6xx, PT6964, HBS658, AIP16xx and related chips. + * + * Copyright (C) 2024 Jean-Fran=C3=A7ois Lessard + */ + +#include +#include +#include + +#include "tm16xx.h" + +#define TM16XX_SPI_BUFFER_SIZE 8 +#define TM16XX_SPI_TWAIT_US 2 + +static int tm16xx_spi_probe(struct spi_device *spi) +{ + const struct tm16xx_controller *controller; + struct tm16xx_display *display; + int ret; + + controller =3D spi_get_device_match_data(spi); + if (!controller) + return -EINVAL; + + display =3D devm_kzalloc(&spi->dev, sizeof(*display), GFP_KERNEL); + if (!display) + return -ENOMEM; + + /* Allocate DMA-safe buffer */ + display->spi_buffer =3D devm_kzalloc(&spi->dev, TM16XX_SPI_BUFFER_SIZE, + GFP_KERNEL); + if (!display->spi_buffer) + return -ENOMEM; + + display->client.spi =3D spi; + display->dev =3D &spi->dev; + display->controller =3D controller; + + spi_set_drvdata(spi, display); + + ret =3D tm16xx_probe(display); + if (ret) + return ret; + + return 0; +} + +static void tm16xx_spi_remove(struct spi_device *spi) +{ + struct tm16xx_display *display =3D spi_get_drvdata(spi); + + tm16xx_remove(display); +} + +/** + * tm16xx_spi_read() - SPI read helper for controller + * @display: pointer to tm16xx_display + * @cmd: command to send + * @cmd_len: length of command + * @data: buffer for received data + * @data_len: length of data to read + * + * Return: 0 on success, negative error code on failure + */ +static int tm16xx_spi_read(struct tm16xx_display *display, u8 *cmd, + size_t cmd_len, u8 *data, size_t data_len) +{ + struct spi_device *spi =3D display->client.spi; + struct spi_message msg; + int ret; + + dev_dbg(display->dev, "spi_write %*ph", (char)cmd_len, cmd); + + /* If STB is high during transmission, command is invalid. + * Reading requires a minimum 2 microseconds wait (Twait) + * after the 8th CLK rising edge before reading on falling edge. + */ + struct spi_transfer xfers[2] =3D { + { + .tx_buf =3D cmd, + .len =3D cmd_len, + .cs_change =3D 0, /* NO CS toggle */ + .delay.value =3D TM16XX_SPI_TWAIT_US, + .delay.unit =3D SPI_DELAY_UNIT_USECS, + }, { + .rx_buf =3D data, + .len =3D data_len, + } + }; + + spi_message_init_with_transfers(&msg, xfers, ARRAY_SIZE(xfers)); + + ret =3D spi_sync(spi, &msg); + + dev_dbg(display->dev, "spi_read %*ph", (char)data_len, data); + + return ret; +} + +/** + * tm16xx_spi_write() - SPI write helper for controller + * @display: pointer to tm16xx_display + * @data: data to write + * @len: number of bytes to write + * + * Return: 0 on success, negative error code on failure + */ +static int tm16xx_spi_write(struct tm16xx_display *display, u8 *data, size= _t len) +{ + dev_dbg(display->dev, "spi_write %*ph", (char)len, data); + + struct spi_device *spi =3D display->client.spi; + + return spi_write(spi, data, len); +} + +/* SPI controller-specific functions */ +static int tm1628_init(struct tm16xx_display *display) +{ + const enum led_brightness brightness =3D display->main_led.brightness; + const u8 num_grids =3D display->num_grids; + u8 *cmd =3D display->spi_buffer; + int ret; + + /* Set mode command based on grid count */ + cmd[0] =3D TM16XX_CMD_MODE; + if (num_grids <=3D 4) + cmd[0] |=3D TM16XX_MODE_4GRIDS; + else if (num_grids =3D=3D 5) + cmd[0] |=3D TM16XX_MODE_5GRIDS; + else if (num_grids =3D=3D 6) + cmd[0] |=3D TM16XX_MODE_6GRIDS; + else + cmd[0] |=3D TM16XX_MODE_7GRIDS; + + ret =3D tm16xx_spi_write(display, cmd, 1); + if (ret < 0) + return ret; + + /* Set data command */ + cmd[0] =3D TM16XX_CMD_WRITE | TM16XX_DATA_ADDR_AUTO; + ret =3D tm16xx_spi_write(display, cmd, 1); + if (ret < 0) + return ret; + + /* Set control command with brightness */ + cmd[0] =3D TM16XX_CMD_CTRL | + TM16XX_CTRL_BRIGHTNESS(brightness, brightness - 1, TM16XX); + ret =3D tm16xx_spi_write(display, cmd, 1); + if (ret < 0) + return ret; + + return 0; +} + +static int tm1618_data(struct tm16xx_display *display, u8 index, + unsigned int grid) +{ + u8 *cmd =3D display->spi_buffer; + + cmd[0] =3D TM16XX_CMD_ADDR + index * 2; + cmd[1] =3D FIELD_GET(TM1618_BYTE1_MASK, grid); + cmd[2] =3D FIELD_GET(TM1618_BYTE2_MASK, grid) << TM1618_BYTE2_SHIFT; + + return tm16xx_spi_write(display, cmd, 3); +} + +static int tm1628_data(struct tm16xx_display *display, u8 index, + unsigned int grid) +{ + u8 *cmd =3D display->spi_buffer; + + cmd[0] =3D TM16XX_CMD_ADDR + index * 2; + cmd[1] =3D FIELD_GET(TM1628_BYTE1_MASK, grid); + cmd[2] =3D FIELD_GET(TM1628_BYTE2_MASK, grid); + + return tm16xx_spi_write(display, cmd, 3); +} + +static int tm1628_keys(struct tm16xx_display *display) +{ + u8 *cmd =3D display->spi_buffer; + u8 *codes =3D display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(display, cmd, 1, codes, TM1628_KEY_READ_LEN); + if (ret) + return ret; + + /* prevent false readings */ + for (i =3D 0; i < TM1628_KEY_READ_LEN; i++) { + if (codes[i] & ~TM1628_KEY_MASK) + return -EINVAL; + } + + tm16xx_for_each_key(display, row, col) { + int byte =3D col >> 1; + int bit =3D row + ((col & 1) * 3); + bool value =3D !!(codes[byte] & BIT(bit)); + + tm16xx_set_key(display, row, col, value); + } + + return 0; +} + +static int tm1638_keys(struct tm16xx_display *display) +{ + u8 *cmd =3D display->spi_buffer; + u8 *codes =3D display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(display, cmd, 1, codes, TM1638_KEY_READ_LEN); + if (ret) + return ret; + + /* prevent false readings */ + for (i =3D 0; i < TM1638_KEY_READ_LEN; i++) { + if (codes[i] & ~TM1638_KEY_MASK) + return -EINVAL; + } + + tm16xx_for_each_key(display, row, col) { + int byte =3D col >> 1; + int bit =3D (2 - row) + ((col & 1) << 2); + bool value =3D !!(codes[byte] & BIT(bit)); + + tm16xx_set_key(display, row, col, value); + } + + return 0; +} + +static int tm1618_keys(struct tm16xx_display *display) +{ + u8 *cmd =3D display->spi_buffer; + u8 *codes =3D display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(display, cmd, 1, codes, TM1618_KEY_READ_LEN); + if (ret) + return ret; + + /* prevent false readings */ + for (i =3D 0; i < TM1618_KEY_READ_LEN; i++) { + if (codes[i] & ~TM1618_KEY_MASK) + return -EINVAL; + } + + tm16xx_set_key(display, 0, 0, !!(codes[0] & BIT(1))); + tm16xx_set_key(display, 0, 1, !!(codes[0] & BIT(4))); + tm16xx_set_key(display, 0, 2, !!(codes[1] & BIT(1))); + tm16xx_set_key(display, 0, 3, !!(codes[1] & BIT(4))); + tm16xx_set_key(display, 0, 4, !!(codes[2] & BIT(1))); + + return 0; +} + +static int fd620_data(struct tm16xx_display *display, u8 index, + unsigned int grid) +{ + u8 *cmd =3D display->spi_buffer; + + cmd[0] =3D TM16XX_CMD_ADDR + index * 2; + cmd[1] =3D FIELD_GET(FD620_BYTE1_MASK, grid); + cmd[2] =3D FIELD_GET(FD620_BYTE2_MASK, grid) << FD620_BYTE2_SHIFT; + + return tm16xx_spi_write(display, cmd, 3); +} + +static int fd620_keys(struct tm16xx_display *display) +{ + u8 *cmd =3D display->spi_buffer; + u8 *codes =3D display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(display, cmd, 1, codes, FD620_KEY_READ_LEN); + if (ret) + return ret; + + /* prevent false readings */ + for (i =3D 0; i < FD620_KEY_READ_LEN; i++) { + if (codes[i] & ~FD620_KEY_MASK) + return -EINVAL; + } + + tm16xx_set_key(display, 0, 0, codes[0] & BIT(0)); + tm16xx_set_key(display, 0, 1, codes[0] & BIT(3)); + tm16xx_set_key(display, 0, 2, codes[1] & BIT(0)); + tm16xx_set_key(display, 0, 3, codes[1] & BIT(3)); + tm16xx_set_key(display, 0, 4, codes[2] & BIT(0)); + tm16xx_set_key(display, 0, 5, codes[2] & BIT(3)); + tm16xx_set_key(display, 0, 6, codes[3] & BIT(0)); + + return 0; +} + +/* SPI controller definitions */ +static const struct tm16xx_controller tm1618_controller =3D { + .max_grids =3D 7, + .max_segments =3D 8, + .max_brightness =3D 8, + .max_key_rows =3D 1, + .max_key_cols =3D 5, + .init =3D tm1628_init, + .data =3D tm1618_data, + .keys =3D tm1618_keys, +}; + +static const struct tm16xx_controller tm1620_controller =3D { + .max_grids =3D 6, + .max_segments =3D 10, + .max_brightness =3D 8, + .max_key_rows =3D 0, + .max_key_cols =3D 0, + .init =3D tm1628_init, + .data =3D tm1628_data, + .keys =3D NULL, +}; + +static const struct tm16xx_controller tm1628_controller =3D { + .max_grids =3D 7, + .max_segments =3D 14, /* seg 11 unused */ + .max_brightness =3D 8, + .max_key_rows =3D 2, + .max_key_cols =3D 10, + .init =3D tm1628_init, + .data =3D tm1628_data, + .keys =3D tm1628_keys, +}; + +static const struct tm16xx_controller tm1638_controller =3D { + .max_grids =3D 8, + .max_segments =3D 10, + .max_brightness =3D 8, + .max_key_rows =3D 3, + .max_key_cols =3D 8, + .init =3D tm1628_init, + .data =3D tm1628_data, + .keys =3D tm1638_keys, +}; + +static const struct tm16xx_controller fd620_controller =3D { + .max_grids =3D 5, + .max_segments =3D 8, + .max_brightness =3D 8, + .max_key_rows =3D 1, + .max_key_cols =3D 7, + .init =3D tm1628_init, + .data =3D fd620_data, + .keys =3D fd620_keys, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id tm16xx_spi_of_match[] =3D { + { .compatible =3D "titanmec,tm1618", .data =3D &tm1618_controller }, + { .compatible =3D "titanmec,tm1620", .data =3D &tm1620_controller }, + { .compatible =3D "titanmec,tm1628", .data =3D &tm1628_controller }, + { .compatible =3D "titanmec,tm1638", .data =3D &tm1638_controller }, + { .compatible =3D "fdhisi,fd620", .data =3D &fd620_controller }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tm16xx_spi_of_match); +#endif + +static const struct spi_device_id tm16xx_spi_id[] =3D { + { "tm1618", (kernel_ulong_t)&tm1618_controller }, + { "tm1620", (kernel_ulong_t)&tm1620_controller }, + { "tm1628", (kernel_ulong_t)&tm1628_controller }, + { "tm1638", (kernel_ulong_t)&tm1638_controller }, + { "fd620", (kernel_ulong_t)&fd620_controller }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(spi, tm16xx_spi_id); + +static struct spi_driver tm16xx_spi_driver =3D { + .driver =3D { + .name =3D "tm16xx-spi", + .of_match_table =3D of_match_ptr(tm16xx_spi_of_match), + }, + .probe =3D tm16xx_spi_probe, + .remove =3D tm16xx_spi_remove, + .shutdown =3D tm16xx_spi_remove, + .id_table =3D tm16xx_spi_id, +}; +module_spi_driver(tm16xx_spi_driver); + +MODULE_AUTHOR("Jean-Fran=C3=A7ois Lessard"); +MODULE_DESCRIPTION("TM16xx-spi LED Display Controllers"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("TM16XX"); --=20 2.43.0