From nobody Sat Oct 4 03:15:46 2025 Received: from mail-qt1-f172.google.com (mail-qt1-f172.google.com [209.85.160.172]) (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 D726C2E282E; Wed, 20 Aug 2025 16:31:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.160.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707486; cv=none; b=SDyXni0LDzOP9X+7YyNvYTpPkAoZdFd22w8WnIoxgMDCR15WY1tzELaC1NUIKhrMIfzQ5X2lb7DHFN73PiLWLkCpmHqyvAwbGJb2mqvtB5QWMWiAv5PDVXo6pEBS7lDPjolsWH2K4xMQ4bWvQrW/kmsrF/3bko8E5G1O7gaZyvk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707486; c=relaxed/simple; bh=UJfS8XsbtLkYyNyl2nPoja3lTDj9uwhXQ7vLXYdofCE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=QdBhGKACI6qFFM0Gx1zSet+0P3lZ8yhPYWXzzC1GV513v6mX3OP2CO2e57v14+NMC74hV4QxmWu7FnCuTIualZ7crPZ3Ptyd6xFhnVeu6L5Y53/i+GCTA+0oIF58DZdSqaW70Y9a3H9uNgSIXKh/coKW/AaPObPTrAUk29q1RUw= 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=ZAft0SvA; arc=none smtp.client-ip=209.85.160.172 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="ZAft0SvA" Received: by mail-qt1-f172.google.com with SMTP id d75a77b69052e-4b109c59dc9so1008551cf.3; Wed, 20 Aug 2025 09:31:24 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755707484; x=1756312284; 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=tXM30wff4XIHChJ3j9G53oUhXmUQwKFOpC/ECdliRHA=; b=ZAft0SvAVfP7C9JzXbdIs4Cgpzgnx3O8HhJb8qTAY4pwLnG8f+NCfoWeIEa95kkXw8 3kQlaKx44L8f/IctIF/D9yL3pC6g3dhIOEt3c87JnrE60aruEkv1ERyJGpSpUQRB1+YD wyf+OfET03WcJy/v7JZrp4QbBYg0NEQqezsgMF9tERjTtuOZwtpKdmbvnklWrZ1iCdbA c/2zCFZGEDmXCnBi5YAwQNpH9Qli+p8h1WphqvL5ttXcnd4END1dCmxgvvHfX6B1YEFp d9/a9J2oG+CbMvtiLuooJ1/02WKvKsQFAgxWedK0wtSjOu4Lfp4OtJhOvhndwpdKNEeo xlHw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755707484; x=1756312284; 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=tXM30wff4XIHChJ3j9G53oUhXmUQwKFOpC/ECdliRHA=; b=f5EV4hpTAfSudg5AZvw1NfNlLx1B5fBpg1+pJy02ICumxNMkTFPbcTSysGm8N+ImBk E9dohf0orQ+xePV8Cqy6XsZQ4AuReih86IdLn+AML08CbnL1z6A3Arn0vT0dSshCyllQ MpsJUgqUdXicqrEwV+5lslqbD1cQQXqhSRQRS+DE7eaUEeE7xuIQ96ccKkglMygDeTZ0 mDV8/eq/zx8txFSDIaEjoUzp2ClnKslIzH0dVbMVOkeAbXkL/IaYEDAE0CVSvh2IomNd bJhimzJIUR8gk1Il8S/Yr3T4Ik1pAo5E3db6rzpCgjaUYBshI3qO4OQqpbx41M9ktSe+ eELQ== X-Forwarded-Encrypted: i=1; AJvYcCURJ/p5N0TjefA3td6fSRv34sVZBZg103dQqKHPkWhotqy3vgtHnLA4WcpDz3g4J/nqD86EbLuO/t6Ceghr@vger.kernel.org, AJvYcCVI5jwqkr1Pz6Ohsnhr/srxAxlPF3UQVgLZEs2nefWq/dNsuGRkcGscCbJETlMg5il19UUdmeoL6mC8@vger.kernel.org, AJvYcCXmMO/A5fvhjKeZBaEbH5S6iiUehmDK9pPeuJOtg3ftP+A/IfwrE+SFlcyeJQialK+26utfcExRfoClKw==@vger.kernel.org X-Gm-Message-State: AOJu0YyRTU285AYLDaqN6cCQPaR2iqoUaLjYfzmm3a+XKUigC2FKeMIo LCZFWWGhq4AVOCgYAF9SFciomJwoa824cbIuocvSPSa/S9WkclteZR2mTJMKueI6 X-Gm-Gg: ASbGncuRbZuG0yh7syb8Vv+IjImlGjXxuD6L9C5wFTxKlJbbjQ9wvPpIDt5+7nkFbXP RdagqZVKIc8zXr7LR3ke76HNZV4EKUK9H1PVH5U7WUjjaCFngippk213x8rLBVj8BM7VlSZKSkO PWPUrjdFaED97oetpO6QP5OXy/F0DI1k7v0cMxqeg0O5V0fl8mHH5X/LkzKaJEe7jRj1N7vIWUU AD1rjiMRjTTXdh55IwM6ClTR9z368y+XnFFe+vs/wXANARXEwuVX4G4RCL6O8o+DHc7dLUdhrOf nLIbtv7e44aEXj2Yikz8uu1+pH0JmDgyHTGhSHHp3i4xeGtHIkzhpGxos1pTZtqTFHBb8l48vDu 9mmmm45g3sH+GmxfGlC3+YtwVmNmRL2ZEc4URTCn0L2pAHbZi3GQlgjzcEc6qS4UKKg7T X-Google-Smtp-Source: AGHT+IG9C1PRQ7PdZF1ZiMg73cQhIVvN5bdIL/FxGPGt/C9F/8Mpp1lbimjL6Pcpk83tkcRya/4o8g== X-Received: by 2002:ac8:5812:0:b0:4b2:8ac4:f089 with SMTP id d75a77b69052e-4b291bfeca7mr39194401cf.71.1755707483496; Wed, 20 Aug 2025 09:31:23 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id af79cd13be357-7e87e195e49sm982452485a.43.2025.08.20.09.31.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Aug 2025 09:31:22 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Geert Uytterhoeven , devicetree@vger.kernel.org, linux-leds@vger.kernel.org, linux-kernel@vger.kernel.org, =?UTF-8?q?Andreas=20F=C3=A4rber?= , Boris Gjenero , Christian Hewitt , Heiner Kallweit , Paolo Sabatino , Martin Blumenstingl Subject: [PATCH v3 1/4] dt-bindings: vendor-prefixes: Add fdhisi, titanmec, princeton, winrise, wxicore Date: Wed, 20 Aug 2025 12:31:14 -0400 Message-ID: <20250820163120.24997-2-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250820163120.24997-1-jefflessard3@gmail.com> References: <20250820163120.24997-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: - 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 e= ither 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 Signed-off-by: Jean-Fran=C3=A7ois Lessard Acked-by: Conor Dooley --- 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 Sat Oct 4 03:15:46 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 71CE332BF2D; Wed, 20 Aug 2025 16:31:26 +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=1755707488; cv=none; b=iD7tDV9T6xRRwhEDpA6CxbUhOceOYIxp7j2Rofi72h73IAkJqIcBJZ7Se5LWsnwHRdkeVzQ7AdeZzluYOj4z3H5BKK8I47TbAY7E91LKmWhrSaxf/6rk9FHEGWEqAomgwiWXPeo+lhYYto94bKszxkn6OtJprqVe5W9xrQOc9g4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707488; c=relaxed/simple; bh=8hLM9138BfaI8hiPrh0JEeh0edD0emDkI3h7JxkHkAU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=RvBS0ehXhKmoN0afeROLhmFw+EE4Vve6zbNWFAwUwOH+nbziqrN9b1LnkKWWANez4bOutROWLtOc4SP3wFcIpThRuMjufQVOrJFMrIbSSjNRtOK7H4gFf2s9QFvSPCLHBebxuhgCCAwnyJz/yUzAd+mSC+4Bdu+79K4ijz+Fht8= 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=boo+cpXU; 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="boo+cpXU" Received: by mail-qt1-f176.google.com with SMTP id d75a77b69052e-4b133b24e69so447541cf.2; Wed, 20 Aug 2025 09:31:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755707485; x=1756312285; 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=ZqSNp4qNmUZDuJr95EJj43cjAsSRT3ThjtTX5VnzbhI=; b=boo+cpXURJmAyDSwE/+enyE6JtZbuO0irQg+3pYE+cgqrK5/drwsXwt+COmEcq1U1O ppVKGC8BRHKj5vWpeaVu/fyj5egycvWBkbRUerkhSOZmIe2+0m+iqCNgfu770pHlGxtH MI5MTe+lao7DjRBcsPWyFIlcnRzTQrpx1iebHdCi4HnkkxN9Y8bOhFEZ3+5IoOxnZf0a W5G6wSdpVotdmCu8HrB0bKKZ5/tNjsP967VBorm2P3AhsZeGyRT3sU+up41um4NocebF mO182+ZKV0XZ7cDPgy5UOoVmrIv9fpgAD/k6MwK46rraQ0ShKV15B1S3G62lN9B1LcCP trEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755707485; x=1756312285; 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=ZqSNp4qNmUZDuJr95EJj43cjAsSRT3ThjtTX5VnzbhI=; b=YAa+XIq9Ticb5FwFFgnwfW3uLoY8skiC5VT84hOfp8WkK2Id9ayd9pof2mZWWoyw7e V4cbYh7eCCHyE2KyKBoxCRbgLBSiaVPCm/aEumUnyT3ChYq43iBu3NSOOjC6/xq/ySoa AK48Gm3VD+o28g6jlM404hvf2AIs2nGGw9H6nsmTqfOcFI5UFloQAyl0kGyA1H3u1ENw hvvAzb4+U5w11/SfHOai/WkbZvyJH+1muLgXBuh0UMYeSjpOz/os421bix6gDDXLAu6O LAjE2twvgqykZ9J3LxLeaxB3KT9srJk9MGyoPRa/CqmdlUpGWmGrU9JHj6RxeDBaP7ui CcxA== X-Forwarded-Encrypted: i=1; AJvYcCVG8gJ5U/7JUpmCvNEC0CsNE3fuRH1Q5onxtbgk9AXxGyuvAxY3Q8ljxfrugunsfh4SRAJeb/P60BRSdg==@vger.kernel.org, AJvYcCVp2YsWu7kylDyohlYgguKs1tdDH4hI1+Mbi3D1baqX8oPohqwKSNDlMIUUAEthCIXDWZxb5PHg9SQiboOy@vger.kernel.org, AJvYcCXLaaNSLAQT2B8IuQatnq/gyYxSV18L34ulZNxcC+mD7z6V6q0AM3d0S/dFyIv7TdtCexGuJYADRI8k@vger.kernel.org X-Gm-Message-State: AOJu0Yz2W56Fdf56Ex2gjneTa79wH29JfVigUsTICqVFAYt1lc3qDJm7 pv7XnRHricS+21MqQ18rxmoQnjdsKg+brTe5100hS1eDZ7J+3fYP0hBT X-Gm-Gg: ASbGncsJEh8Fvm8KNhSvBhQZBrk5CNc2iUhw3N4TiXswfz4TlzPwspn+gHOW5WiIJQX VNESZY9sX6TffX9AthVRjBC36m5uQaDF/34Ar4EEMmqIFqdVg8U4ezGk4CnnlwFAYInee22tzTz FnYAW+ZSjHBO1QlFVjmAf+4d0D1z/to/Rcejj0DWpzSpnDzlTIopaB3GHMZKEfX+Ka9aeUyz+2/ ogXIxK3ismrmQDN2oI8HF8ebY9pCPrq+nNhm7dmyUDOukM0o3loQ16wRyJ/O+RJTxtCx34mFTxu g1VJWOxQkuc0CT7TjwVcxzhj0+diTjok7hgcuTNN2SBmhS+5yB10jMGS7xYG+La0P2pJRdmu6zr Kijo8+eK9c5GRM6p5cTEW6bYQmSLXBQJhlywtDm77jFsigG7sCcuZfUGoHnHRTsFB+q3g X-Google-Smtp-Source: AGHT+IGGkLUR1nvtsxzHrxCZXG7rturre4GTALvYyftxMgcjWKB/0JZ/EFPLrYgLTxqwffRSirOj5g== X-Received: by 2002:a05:622a:244b:b0:4af:1daa:11ee with SMTP id d75a77b69052e-4b291a6b863mr50422151cf.16.1755707484924; Wed, 20 Aug 2025 09:31:24 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-70ba9097688sm89533436d6.25.2025.08.20.09.31.24 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Aug 2025 09:31:24 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Geert Uytterhoeven , devicetree@vger.kernel.org, linux-leds@vger.kernel.org, linux-kernel@vger.kernel.org, =?UTF-8?q?Andreas=20F=C3=A4rber?= , Boris Gjenero , Christian Hewitt , Heiner Kallweit , Paolo Sabatino , Martin Blumenstingl Subject: [PATCH v3 2/4] dt-bindings: auxdisplay: add Titan Micro Electronics TM16xx Date: Wed, 20 Aug 2025 12:31:15 -0400 Message-ID: <20250820163120.24997-3-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250820163120.24997-1-jefflessard3@gmail.com> References: <20250820163120.24997-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 w= ith keyscan. Signed-off-by: Jean-Fran=C3=A7ois Lessard --- Note: The 'segments' property is intentionally not vendor-prefixed as it de= fines 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 generic= ally, 'segments' describes segment connections in a standardized way using uint32-matrix format. .../bindings/auxdisplay/titanmec,tm16xx.yaml | 471 ++++++++++++++++++ 1 file changed, 471 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..b563c6e1e --- /dev/null +++ b/Documentation/devicetree/bindings/auxdisplay/titanmec,tm16xx.yaml @@ -0,0 +1,471 @@ +# 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 + + Optional keypad scanning is supported when both 'linux,keymap' and + 'poll-interval' properties are specified. + +properties: + compatible: + oneOf: + # Controllers with titanmec,tm1628 fallback + - items: + - enum: + - fdhisi,fd628 + - princeton,pt6964 + - wxicore,aip1628 + - const: titanmec,tm1628 + - const: titanmec,tm1628 + # Controllers with titanmec,tm1618 fallback + - items: + - enum: + - wxicore,aip1618 + - const: titanmec,tm1618 + - const: titanmec,tm1618 + # Controllers with titanmec,tm1650 fallback + - items: + - enum: + - fdhisi,fd650 + - wxicore,aip650 + - const: titanmec,tm1650 + - const: titanmec,tm1650 + # Canonical standalone controllers + - const: fdhisi,fd620 + - const: fdhisi,fd655 + - const: fdhisi,fd6551 + - const: titanmec,tm1620 + - const: titanmec,tm1638 + - const: winrise,hbs658 + + reg: + maxItems: 1 + + label: + description: Name of the entire device + default: display + + 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 + + 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/types.yaml#/definitions/uint32 + + linux,keymap: + $ref: /schemas/input/matrix-keymap.yaml#/properties/linux,keymap + + poll-interval: + $ref: /schemas/input/input.yaml#/properties/poll-interval + + autorepeat: + $ref: /schemas/input/input.yaml#/properties/autorepeat + + digits: + type: object + description: Container for 7-segment digit group definitions + properties: + "#address-cells": + const: 1 + "#size-cells": + const: 0 + + patternProperties: + "^digit@[0-9]+$": + type: object + 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 + unevaluatedProperties: false + + additionalProperties: false + + leds: + type: object + description: Container for individual LED icon definitions + properties: + "#address-cells": + const: 2 + "#size-cells": + const: 0 + + patternProperties: + "^led@[0-9]+,[0-9]+$": + type: object + $ref: /schemas/leds/common.yaml# + properties: + reg: + description: + Grid and segment indices as of this individua= l LED icon + required: + - reg + unevaluatedProperties: false + + additionalProperties: false + +# 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 + +dependencies: + poll-interval: + - linux,keymap + linux,keymap: + - poll-interval + autorepeat: + - linux,keymap + - poll-interval + +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-controller@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-controller@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-controller@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"; + }; + }; + }; + }; --=20 2.43.0 From nobody Sat Oct 4 03:15:46 2025 Received: from mail-qv1-f50.google.com (mail-qv1-f50.google.com [209.85.219.50]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C4523327794; Wed, 20 Aug 2025 16:31:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.50 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707518; cv=none; b=WCOz8171rwCmZ3dr++hl/ePKpiCLJvgGW7grMk3gc/u69aSB2NIlOdNDKf7YS2kluEpGuPY86LPV/IFLAosKpin0t19l+IHcIMQw74d8j1I+Q6NyZg0Nlrz9zevDCXSw0LAuf864Xvry/fZIErKbeA6N2tFACcdtCyvmLIEaf4c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707518; c=relaxed/simple; bh=HZjaAUPMAAvGtfaiXZoELi9cO2RZzw1xH8NwDGGXaT0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=f8JJrhpkcKGZmRzDmoiz/2TVcAwx+W8gbNu1l9s72ifsvI14CV7ixro/r6lSZw+MOVapYX7tVLX3bmkdFHrx6J9AHrgfPpfQp6V+ypUmXyi1MbOi/HdNQrt451uccA0ZSEIwhhIemNyqu3O8KUT8CQQjkFNIc3lAoVIP4UkD69E= 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=ksSSD31c; arc=none smtp.client-ip=209.85.219.50 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ksSSD31c" Received: by mail-qv1-f50.google.com with SMTP id 6a1803df08f44-70bb007a821so1758706d6.0; Wed, 20 Aug 2025 09:31:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755707514; x=1756312314; 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=oYDFp8D3tvU/xodVJdpW5cmO/0u3arhq65/XOkuHt+Q=; b=ksSSD31cIwDSwzFaBbHTjKBrDb22CGRbOS6Jxa2u3M8YC1KoZ2QelLHy8aTikLWs4z D8QVrWD9w8PsLMcxMsO7bWkXFa/z2+TYCuLhRhiKj0hNd9v1ewlKob4WF6e+WEqqwaUG /Pagx1CNfYfB1lhrfzAUVd3ZFhyvCU8Z4iZl1ItTgd/zutZyScaOZdpBMiSwfW3YG1zW fneo4YZjuLr7VVxbdjj+x8uZPHoE+RNFqt8ZJYV3kRjHSpWIs5bi8WleiCg8FSgqApaK ImqS/T6pXVlcCbhCN0feEM5FL/sQep+/ztLVgjkxHyKYkvbqyy1RHZ9viZLUlBNAhv+Z XE4Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755707514; x=1756312314; 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=oYDFp8D3tvU/xodVJdpW5cmO/0u3arhq65/XOkuHt+Q=; b=lY1UPirK87kxFwvp4DoI53khK0MXcsAdE+huZRnS8KrifetwlPQ42562NHxx3vOGK+ 9C6EgmGJ7P9N1fhjS6oDWmAfQSeDRNF8nOaWe+au+erN9j4eqyKtllmQiqNPejg/I0I3 MbKy6nT6dkRMpxKD9H9eokMcBMW0go3HUxR963Ao2NQWUJMuak8ksrL/zioVWMBXkhks j2k3HMfQsXvhsof4luh30Op6r1ORpBeN41XBKr6WyewLX5XHGtCCkt2M+vZg5bIwBdSJ Utj6FqsRJm6UesiE5CRFenKkVW7KPHk7tp3MJ7F3rokyBMjHXbdWdZMdxXJhVzVxS/Qp n8qQ== X-Forwarded-Encrypted: i=1; AJvYcCWB2VH+xvN9wmCkPJ7h+Gt12/zZkP4TJhVK2asMmlGM51oBaKkoYWb52ZwNRRsu+N9AeI27cTaMgxLAzQ6l@vger.kernel.org, AJvYcCXBgHH50cjK+uHmfH3bVn54OFecYDiHFX2uBt7VNTTnttpbORNuuZyrNygk9VI8vgryJfXOn9XhhK0s/A==@vger.kernel.org, AJvYcCXXGiUrkj0LySg+Jh2vJGJihi+PJLcqNkzP14so+xYIpcbp4MkTryNr298rJerZn82pVyRiQ3vqobos@vger.kernel.org X-Gm-Message-State: AOJu0YwYoKKk9gK1V510pjGLDmGsHdEBE9tmT4nm347tZuShWFbE1VMm xJ4BgBh1WPs9/fqlzlQzvz7UwAcHwRkmrD/TQ8Rod993My2TObmmJYg7 X-Gm-Gg: ASbGnctF1w1gM841RPjLIKMHnNuSkDCGDKOHWHq8gOgJowH1a4RhccDZ0xhxfCJcb1/ 1UpKClbEN1TLWQZbBHCZP4uU4p8+Wk3FJ1Q06atTaOW8BHnAo0w4xPupSRTXkn0nEQV4OvDccp0 4Usi5hGnk1HkFFVNPbSlpD+qtcXt79pOwhxqz/n+hqUER5vhyeyO0gqu+2XKbI1qXky5wlljX8m gg1jlmW880tJBGKfkQnmFsrmigygqXYrIn1A+nu+GdKC+dSXPFUxnG0kBgYV1HTXFd813oYYf+u mMa/rbME9iHQL+b5dI3Yls/A2TrrjpM9kDkscZXT01gsXSf9SM3v+aPIfbyJ8Mcfrno+dCkTtae 67pPcSTXWjeh381b9lvT104cgtIU6wUglrHkkXJOPwS58Eyc7W8FLq7Wo/NhXKNzJE5jTNiHMWj oGRAI= X-Google-Smtp-Source: AGHT+IGW4hu/AwwXkVQrGwmClG+LAqiuFtjTvkuB67OF9mLjcPpvEFAKnCuCgKXcEhxg1D9NT76RGA== X-Received: by 2002:a05:6214:1d22:b0:70d:6df4:1b08 with SMTP id 6a1803df08f44-70d7714bfbfmr37870786d6.54.1755707486511; Wed, 20 Aug 2025 09:31:26 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-70ba9300effsm88143776d6.37.2025.08.20.09.31.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Aug 2025 09:31:26 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Geert Uytterhoeven , devicetree@vger.kernel.org, linux-leds@vger.kernel.org, linux-kernel@vger.kernel.org, =?UTF-8?q?Andreas=20F=C3=A4rber?= , Boris Gjenero , Christian Hewitt , Heiner Kallweit , Paolo Sabatino , Martin Blumenstingl Subject: [PATCH v3 3/4] auxdisplay: Add TM16xx 7-segment LED matrix display controllers driver Date: Wed, 20 Aug 2025 12:31:16 -0400 Message-ID: <20250820163120.24997-4-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250820163120.24997-1-jefflessard3@gmail.com> References: <20250820163120.24997-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 mult= iple vendors including Titan Micro, Fuda Hisi, i-Core, Princeton, and Winrise. These controllers drive 7-segment digits and individual LED icons through e= ither I2C or SPI interfaces with optional keypad scanning support. 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 Acked-by: may also be used by other stakeholders, such as people with domain --- Note: checkpatch reports several false positives that are intentionally ignored: - COMPLEX_MACRO/MACRO_ARG_REUSE for 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. - DEVICE_ATTR_FUNCTIONS: Functions are correctly prefixed with driver name (tm16xx_*) following standard kernel practice for device attribute functions to avoid namespace conflicts. drivers/auxdisplay/Kconfig | 20 + drivers/auxdisplay/Makefile | 1 + drivers/auxdisplay/tm16xx.c | 1781 +++++++++++++++++++++++++++++++++++ 3 files changed, 1802 insertions(+) create mode 100644 drivers/auxdisplay/tm16xx.c diff --git a/drivers/auxdisplay/Kconfig b/drivers/auxdisplay/Kconfig index bedc6133f..54c4c43c0 100644 --- a/drivers/auxdisplay/Kconfig +++ b/drivers/auxdisplay/Kconfig @@ -526,6 +526,26 @@ 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 with keyscan" + depends on SPI || I2C + depends on INPUT + select NEW_LEDS + select LEDS_CLASS + select LEDS_TRIGGERS + select INPUT_MATRIXKMAP + 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..a2444cfb7 100644 --- a/drivers/auxdisplay/Makefile +++ b/drivers/auxdisplay/Makefile @@ -16,3 +16,4 @@ 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 diff --git a/drivers/auxdisplay/tm16xx.c b/drivers/auxdisplay/tm16xx.c new file mode 100644 index 000000000..808e664f8 --- /dev/null +++ b/drivers/auxdisplay/tm16xx.c @@ -0,0 +1,1781 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TM16XX_DRIVER_NAME "tm16xx" +#define TM16XX_DEVICE_NAME "display" +#define TM16XX_DIGIT_SEGMENTS 7 +#define TM16XX_SPI_BUFFER_SIZE 8 +#define CH34XX_SPI_TWAIT_US 2 + +/* Common bit field definitions */ + +/* Command type bits (bits 7-6) */ +#define TM16XX_CMD_MASK GENMASK(7, 6) +#define TM16XX_CMD_MODE 0 +#define TM16XX_CMD_DATA BIT(6) +#define TM16XX_CMD_CTRL BIT(7) +#define TM16XX_CMD_ADDR (BIT(7) | BIT(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 +#define TM16XX_MODE_5GRIDS BIT(0) +#define TM16XX_MODE_6GRIDS BIT(1) +#define TM16XX_MODE_7GRIDS (BIT(1) | BIT(0)) + +/* Data command settings */ +#define TM16XX_DATA_ADDR_MASK BIT(2) +#define TM16XX_DATA_ADDR_AUTO 0 +#define TM16XX_DATA_ADDR_FIXED BIT(2) +#define TM16XX_DATA_MODE_MASK GENMASK(1, 0) +#define TM16XX_DATA_MODE_WRITE 0 +#define TM16XX_DATA_MODE_READ BIT(1) + +/* Control command settings */ +#define TM16XX_CTRL_ON BIT(3) +#define TM16XX_CTRL_BR_MASK GENMASK(2, 0) + +/* 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 BIT(0) +#define TM1650_CTRL_SLEEP BIT(2) +#define TM1650_CTRL_SEG_MASK BIT(3) +#define TM1650_CTRL_SEG8_MODE 0 +#define TM1650_CTRL_SEG7_MODE BIT(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 BIT(0) + +#define FD6551_CMD_CTRL 0x48 +#define FD6551_CTRL_BR_MASK GENMASK(3, 1) +#define FD6551_CTRL_ON BIT(0) + +#define HBS658_KEY_COL_MASK GENMASK(7, 5) + +#define TM16XX_CTRL_BRIGHTNESS(enabled, value, prefix) \ + ((enabled) ? (FIELD_PREP(prefix##_CTRL_BR_MASK, (value)) | \ + prefix##_CTRL_ON) : 0) + +static char *default_value; +module_param(default_value, charp, 0444); +MODULE_PARM_DESC(default_value, "Default display value to initialize"); + +static SEG7_CONVERSION_MAP(map_seg7, MAP_ASCII7SEG_ALPHANUM); + +/* Forward declarations */ +struct tm16xx_display; +struct tm16xx_keypad; + +/** + * 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, u16 data); + int (*const keys)(struct tm16xx_keypad *keypad); +}; + +/** + * 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; +}; + +/** + * 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; +}; + +/** + * struct tm16xx_keypad - Keypad matrix state and input device + * @display: Backpointer to owning display structure. + * @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 tm16xx_display *display; + struct input_dev *input; + unsigned long *state; + unsigned long *last_state; + unsigned long *changes; + u8 row_shift; +}; + +/* 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 + * @grid: grid index + * + * Return: bit pattern of all segments for the given grid + */ +static inline u16 tm16xx_get_grid(const struct tm16xx_display *display, + const unsigned int grid) +{ + return (u16)bitmap_read(display->state, grid * display->num_segments, + display->num_segments); +} + +/** + * tm16xx_key_nbits() - Number of bits for the keypad state bitmap + * @keypad: pointer to tm16xx_keypad + * + * Return: total bits in keypad state bitmap (max_key_rows * max_key_cols) + */ +static inline unsigned int tm16xx_key_nbits(const struct tm16xx_keypad *ke= ypad) +{ + return keypad->display->controller->max_key_rows * + keypad->display->controller->max_key_cols; +} + +/** + * tm16xx_set_key() - Set the keypad state for a key + * @keypad: pointer to tm16xx_keypad + * @row: row index + * @col: column index + * @pressed: true if pressed, false otherwise + */ +static inline void tm16xx_set_key(const struct tm16xx_keypad *keypad, + const u8 row, const u8 col, const bool pressed) +{ + __assign_bit(row * keypad->display->controller->max_key_cols + col, + keypad->state, pressed); +} + +/** + * tm16xx_get_key_row() - Get row index from keypad bit index + * @keypad: pointer to tm16xx_keypad + * @bit: bit index in state bitmap + * + * Return: row index + */ +static inline u8 tm16xx_get_key_row(const struct tm16xx_keypad *keypad, + const unsigned int bit) +{ + return bit / keypad->display->controller->max_key_cols; +} + +/** + * tm16xx_get_key_col() - Get column index from keypad bit index + * @keypad: pointer to tm16xx_keypad + * @bit: bit index in state bitmap + * + * Return: column index + */ +static inline u8 tm16xx_get_key_col(const struct tm16xx_keypad *keypad, + const unsigned int bit) +{ + return bit % keypad->display->controller->max_key_cols; +} + +#define for_each_key(keypad, _r, _c) \ + for (unsigned int (_r) =3D 0; \ + (_r) < (keypad)->display->controller->max_key_rows; (_r)++) \ + for (unsigned int (_c) =3D 0; \ + (_c) < (keypad)->display->controller->max_key_cols; (_c)++) + +/* key scanning */ +/** + * 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_keypad *keypad =3D input_get_drvdata(input); + const unsigned short *keycodes =3D keypad->input->keycode; + unsigned int nbits =3D tm16xx_key_nbits(keypad); + + unsigned int bit; + u8 row, col; + bool pressed; + int ret; + + bitmap_zero(keypad->state, nbits); + bitmap_zero(keypad->changes, nbits); + + mutex_lock(&keypad->display->lock); + ret =3D keypad->display->controller->keys(keypad); + mutex_unlock(&keypad->display->lock); + + if (ret < 0) { + dev_err(keypad->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(keypad, bit); + col =3D tm16xx_get_key_col(keypad, bit); + pressed =3D _test_bit(bit, keypad->state); + u16 scancode =3D MATRIX_SCAN_CODE(row, col, keypad->row_shift); + + dev_dbg(keypad->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 + */ +static 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 =3D 0; + + 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) { + dev_err(display->dev, "Failed to read poll-interval: %d\n", ret); + return ret; + } + + keypad =3D devm_kzalloc(display->dev, sizeof(*keypad), GFP_KERNEL); + if (!keypad) + return -ENOMEM; + keypad->display =3D display; + + nbits =3D tm16xx_key_nbits(keypad); + 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) { + ret =3D -ENOMEM; + goto free_keypad; + } + + input =3D devm_input_allocate_device(display->dev); + if (!input) { + dev_err(display->dev, "Failed to allocate input device\n"); + ret =3D -ENOMEM; + goto free_bitmaps; + } + input->name =3D TM16XX_DRIVER_NAME "-keypad"; + keypad->input =3D input; + input_set_drvdata(input, keypad); + + keypad->row_shift =3D get_count_order(cols); + ret =3D matrix_keypad_build_keymap(NULL, "linux,keymap", rows, cols, NULL, + input); + if (ret < 0) { + dev_err(display->dev, "Failed to build keymap: %d\n", ret); + goto free_input; + } + + 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) { + dev_err(display->dev, "Failed to register input device: %d\n", + ret); + goto free_input; + } + + dev_dbg(display->dev, "keypad rows=3D%u, cols=3D%u, poll=3D%u\n", rows, c= ols, + poll_interval); + + return 0; + +free_input: + input_free_device(input); +free_bitmaps: + devm_kfree(display->dev, keypad->state); + devm_kfree(display->dev, keypad->last_state); + devm_kfree(display->dev, keypad->changes); +free_keypad: + devm_kfree(display->dev, keypad); + return ret; +} + +/* 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) { + mutex_lock(&display->lock); + dev_dbg(display->dev, "Configuring controller\n"); + ret =3D display->controller->init(display); + display->flush_status =3D ret; + mutex_unlock(&display->lock); + 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; + u16 data; + + mutex_lock(&display->lock); + dev_dbg(display->dev, "Sending data to controller\n"); + + if (display->controller->data) { + for (i =3D 0; i < display->num_grids; i++) { + data =3D tm16xx_get_grid(display, i); + ret =3D display->controller->data(display, i, data); + if (ret < 0) { + dev_err(display->dev, + "Failed to write display data: %d\n", + ret); + break; + } + } + } + + display->flush_status =3D ret; + mutex_unlock(&display->lock); +} + +/** + * tm16xx_display_remove() - Remove display, unregister LEDs, blank output + * @display: pointer to tm16xx_display + */ +static void tm16xx_display_remove(struct tm16xx_display *display) +{ + unsigned int nbits =3D tm16xx_led_nbits(display); + struct tm16xx_led *led; + + dev_dbg(display->dev, "Removing display\n"); + + for (int i =3D 0; i < display->num_leds; i++) { + led =3D &display->leds[i]; + devm_led_classdev_unregister(display->dev, &led->cdev); + } + devm_led_classdev_unregister(display->dev, &display->main_led); + + bitmap_zero(display->state, nbits); + schedule_work(&display->flush_display); + flush_work(&display->flush_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"); +} + +/** + * 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 (default_value) { + tm16xx_value_store(display->main_led.dev, NULL, default_value, + strlen(default_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 + */ +static 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) { + dev_err(dev, "Failed to parse device tree: %d\n", ret); + return ret; + } + + nbits =3D tm16xx_led_nbits(display); + display->state =3D devm_bitmap_zalloc(display->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); + + main->name =3D TM16XX_DEVICE_NAME; + main->brightness =3D display->controller->max_brightness; + main->max_brightness =3D display->controller->max_brightness; + device_property_read_string(dev, "label", &main->name); + device_property_read_u32(dev, "max-brightness", &main->max_brightness); + if (main->max_brightness > display->controller->max_brightness) + main->max_brightness =3D display->controller->max_brightness; + device_property_read_u32(dev, "default-brightness", &main->brightness); + if (main->brightness > main->max_brightness) + main->brightness =3D 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 devm_led_classdev_register(dev, &display->main_led); + if (ret < 0) { + dev_err(dev, "Failed to register main LED: %d\n", ret); + return ret; + } + + 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(display->main_led.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 devm_led_classdev_register_ext(dev, &led->cdev, &led_init); + if (ret < 0) { + fwnode_handle_put(child); + dev_err(dev, "Failed to register LED %s: %d\n", + led->cdev.name, ret); + return ret; + } + + i++; + } + + ret =3D tm16xx_display_init(display); + if (ret < 0) { + dev_err(display->dev, "Failed to initialize display: %d\n", ret); + return ret; + } + + ret =3D tm16xx_keypad_probe(display); + if (ret < 0) + dev_warn(display->dev, "Failed to initialize keypad: %d\n", ret); + + return 0; +} + +/* SPI specific code */ +#if IS_ENABLED(CONFIG_SPI_MASTER) +/** + * tm16xx_spi_probe() - Probe callback for SPI-attached controllers + * @spi: pointer to spi_device + * + * Return: 0 on success, negative error code on failure + */ +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; +} + +/** + * tm16xx_spi_remove() - Remove callback for SPI-attached controllers + * @spi: pointer to spi_device + */ +static void tm16xx_spi_remove(struct spi_device *spi) +{ + struct tm16xx_display *display =3D spi_get_drvdata(spi); + + tm16xx_display_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 CH34XX_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, u16 data) +{ + u8 *cmd =3D display->spi_buffer; + + cmd[0] =3D TM16XX_CMD_ADDR + index * 2; + cmd[1] =3D FIELD_GET(TM1618_BYTE1_MASK, data); + cmd[2] =3D FIELD_GET(TM1618_BYTE2_MASK, data) << TM1618_BYTE2_SHIFT; + + return tm16xx_spi_write(display, cmd, 3); +} + +static int tm1628_data(struct tm16xx_display *display, u8 index, u16 data) +{ + u8 *cmd =3D display->spi_buffer; + + cmd[0] =3D TM16XX_CMD_ADDR + index * 2; + cmd[1] =3D FIELD_GET(TM1628_BYTE1_MASK, data); + cmd[2] =3D FIELD_GET(TM1628_BYTE2_MASK, data); + + return tm16xx_spi_write(display, cmd, 3); +} + +static int tm1628_keys(struct tm16xx_keypad *keypad) +{ + u8 *cmd =3D keypad->display->spi_buffer; + u8 *codes =3D keypad->display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(keypad->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; + } + + for_each_key(keypad, row, col) { + int byte =3D col >> 1; + int bit =3D row + ((col & 1) * 3); + bool value =3D !!(codes[byte] & BIT(bit)); + + tm16xx_set_key(keypad, row, col, value); + } + + return 0; +} + +static int tm1638_keys(struct tm16xx_keypad *keypad) +{ + u8 *cmd =3D keypad->display->spi_buffer; + u8 *codes =3D keypad->display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(keypad->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; + } + + for_each_key(keypad, 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(keypad, row, col, value); + } + + return 0; +} + +static int tm1618_keys(struct tm16xx_keypad *keypad) +{ + u8 *cmd =3D keypad->display->spi_buffer; + u8 *codes =3D keypad->display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(keypad->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(keypad, 0, 0, !!(codes[0] & BIT(1))); + tm16xx_set_key(keypad, 0, 1, !!(codes[0] & BIT(4))); + tm16xx_set_key(keypad, 0, 2, !!(codes[1] & BIT(1))); + tm16xx_set_key(keypad, 0, 3, !!(codes[1] & BIT(4))); + tm16xx_set_key(keypad, 0, 4, !!(codes[2] & BIT(1))); + + return 0; +} + +static int fd620_data(struct tm16xx_display *display, u8 index, u16 data) +{ + u8 *cmd =3D display->spi_buffer; + + cmd[0] =3D TM16XX_CMD_ADDR + index * 2; + cmd[1] =3D FIELD_GET(FD620_BYTE1_MASK, data); + cmd[2] =3D FIELD_GET(FD620_BYTE2_MASK, data) << FD620_BYTE2_SHIFT; + + return tm16xx_spi_write(display, cmd, 3); +} + +static int fd620_keys(struct tm16xx_keypad *keypad) +{ + u8 *cmd =3D keypad->display->spi_buffer; + u8 *codes =3D keypad->display->spi_buffer; + int ret, i; + + cmd[0] =3D TM16XX_CMD_READ; + ret =3D tm16xx_spi_read(keypad->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(keypad, 0, 0, codes[0] & BIT(0)); + tm16xx_set_key(keypad, 0, 1, codes[0] & BIT(3)); + tm16xx_set_key(keypad, 0, 2, codes[1] & BIT(0)); + tm16xx_set_key(keypad, 0, 3, codes[1] & BIT(3)); + tm16xx_set_key(keypad, 0, 4, codes[2] & BIT(0)); + tm16xx_set_key(keypad, 0, 5, codes[2] & BIT(3)); + tm16xx_set_key(keypad, 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_DRIVER_NAME, + .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, +}; + +static int tm16xx_spi_register(void) +{ + return spi_register_driver(&tm16xx_spi_driver); +} + +static void tm16xx_spi_unregister(void) +{ + spi_unregister_driver(&tm16xx_spi_driver); +} +#else +static int tm16xx_spi_register(void) +{ + return 0; +} + +static void tm16xx_spi_unregister(void) +{ +} +#endif /* CONFIG_SPI_MASTER */ + +/* I2C specific code */ +#if IS_ENABLED(CONFIG_I2C) +/** + * tm16xx_i2c_probe() - Probe callback for I2C-attached controllers + * @client: pointer to i2c_client + * + * Return: 0 on success, negative error code on failure + */ +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; +} + +/** + * tm16xx_i2c_remove() - Remove callback for I2C-attached controllers + * @client: pointer to i2c_client + */ +static void tm16xx_i2c_remove(struct i2c_client *client) +{ + struct tm16xx_display *display =3D i2c_get_clientdata(client); + + tm16xx_display_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, u16 data) +{ + u8 cmds[2]; + + cmds[0] =3D TM1650_CMD_ADDR + index * 2; + cmds[1] =3D data; // SEG 1 to 8 + + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int tm1650_keys(struct tm16xx_keypad *keypad) +{ + u8 keycode, row, col; + bool pressed; + int ret; + + ret =3D tm16xx_i2c_read(keypad->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(keypad, row, 0, pressed); + tm16xx_set_key(keypad, row, 1, pressed); + } else { + col =3D FIELD_GET(TM1650_KEY_COL_MASK, keycode); + tm16xx_set_key(keypad, 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, u16 data) +{ + u8 cmds[2]; + + cmds[0] =3D FD655_CMD_ADDR + index * 2; + cmds[1] =3D data; // 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, u16 data) +{ + u8 cmds[2]; + + cmds[0] =3D TM16XX_CMD_ADDR + index * 2; + cmds[1] =3D data; + + hbs658_swap_nibbles(cmds, ARRAY_SIZE(cmds)); + return tm16xx_i2c_write(display, cmds, ARRAY_SIZE(cmds)); +} + +static int hbs658_keys(struct tm16xx_keypad *keypad) +{ + u8 cmd, keycode, col; + int ret; + + cmd =3D TM16XX_CMD_READ; + hbs658_swap_nibbles(&cmd, 1); + ret =3D tm16xx_i2c_read(keypad->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(keypad, 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_DRIVER_NAME, + .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, +}; + +static int tm16xx_i2c_register(void) +{ + return i2c_add_driver(&tm16xx_i2c_driver); +} + +static void tm16xx_i2c_unregister(void) +{ + i2c_del_driver(&tm16xx_i2c_driver); +} +#else +static int tm16xx_i2c_register(void) +{ + return 0; +} + +static void tm16xx_i2c_unregister(void) +{ +} +#endif /* CONFIG_I2C */ + +/** + * tm16xx_init() - Module initialization entrypoint + * + * Return: 0 on success, negative error code on failure + */ +static int __init tm16xx_init(void) +{ + int ret; + + ret =3D tm16xx_spi_register(); + if (ret) + return ret; + + ret =3D tm16xx_i2c_register(); + if (ret) { + tm16xx_spi_unregister(); + return ret; + } + + return 0; +} + +/** + * tm16xx_exit() - Module exit/cleanup + */ +static void __exit tm16xx_exit(void) +{ + tm16xx_i2c_unregister(); + tm16xx_spi_unregister(); +} + +module_init(tm16xx_init); +module_exit(tm16xx_exit); + +MODULE_AUTHOR("Jean-Fran=C3=A7ois Lessard"); +MODULE_DESCRIPTION("TM16XX Compatible LED Display Controllers"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Oct 4 03:15:46 2025 Received: from mail-qk1-f176.google.com (mail-qk1-f176.google.com [209.85.222.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 496E2322A1C; Wed, 20 Aug 2025 16:31:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707492; cv=none; b=TI8wiKrwrpW7p7AHADl4KIRJcKugiUwMmUwiPhs/mckdmQoaR1l59Zaf69cBILVy3phZU7QFXQJFMU8dUYaF23xWe7Ewbhy2S2mHVKeKFi1OtPxr7Uo5frYaV8hsdlOKLm+Y3AVwAvyTCNKmSb4Gc7phaXhHMjG9W8eR7UzaOSA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1755707492; c=relaxed/simple; bh=Von3PjjSJyjQTHSFPdYOZi3hAcQqDoIzsdjQSpTQuHM=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=M47rHhNXzryhAxWwsLz1DMKPgO/f4in/2hbb4fX7sk/tS2FPPwEcZpOe+yHju0adZikZ208ZMVxUBcpoxW52vQ7nPjBZ3c5jRo9OuJFrvCh1RcZR34gLhq9JLahccC4Z/SnKjaN6NxuB6eXr3CQBd0dX601EI0UVXcRx/5XKP6I= 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=N2s4HehD; arc=none smtp.client-ip=209.85.222.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="N2s4HehD" Received: by mail-qk1-f176.google.com with SMTP id af79cd13be357-7e864c4615aso119841385a.1; Wed, 20 Aug 2025 09:31:29 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1755707489; x=1756312289; 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=MAwZSi0R2j5en5WCbFrEnUQ0O9ylq9ecPWrmpd6nYHw=; b=N2s4HehDzFrKd8CcMSyH/AFtWnKjey5rQBS3sfxGIAjt4/F4MH1FiCV8VCyzf9h575 FRYLWHSEyWnqy/UgJJhIstw1PKJiaFO+oppPbh9Xb2X93e4ki34fvlVPff1J4il0hFWx eQ4kwXIVz2bSxdutcysA5kPnwTxT3hj3N9MDFtZPeJVALA/CSXm0tH8siQtBl0LnXEAM 7U1WERyCcdSblo4aGVnigxXphJu/wG8M4qgLuSNMgoOofllNzA7q/04bYiLavR6RP0zK trOscKialW1Xmfva0APPD9k9x5KL8oDjtnqcU52gxdgr4ugpsWWIebbTD/ZUbvt1wbOt 0AeQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755707489; x=1756312289; 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=MAwZSi0R2j5en5WCbFrEnUQ0O9ylq9ecPWrmpd6nYHw=; b=UIsWFPP2eCmoXkPJoDKt6fyVDfINIXQJ4kvNK3fgtmr/SuOO6WFYesgBZJa6kOiGQq encSq+kjHDeBVdPjGFx4SlygVUDY9vqxGBXBX56xV4iw8FeUetRLgcVPTYFM6Tde8EKP 2BzkvjEN5q2mWG4y0emK+918tFiwRSZt2DchkJoboFmriH5HmRvE5pFOxFCllzCvj6ey FwUag2fIAdSpCIghvwgW3z/gde7gm+I4NNnlBaWla2IqC4LYHSU0ufGrFXcmVDOJeTYG qwfmIpCPbyimzHy7ZGrziU8scYjfSzXW51bItCHbukE0p+MfeObRM7flAR59+fSs0M0Q 9u5g== X-Forwarded-Encrypted: i=1; AJvYcCUwMZDWx/XsSJS00stqAlgQjJEYP5sRDxrMOaYwrJcKCkxYWEIp1lWmn4v8OxSJ1L2aFMwL8L7vxfxO/Q==@vger.kernel.org, AJvYcCWQD3U/7I9kvCTV7Ji6c2yX6dosykPg16EQdsUZWoCyh/5XmOg1ZHALWE2DCGiBMkGm0so4K/PiigPj@vger.kernel.org, AJvYcCXE/kNtsrCEq63c9v8YhYY0DmZOFOaoaF8MmO6nVds/fny9tCDkwPPUhYS4K7yiXYPIuu8YBuoBF5suLTis@vger.kernel.org X-Gm-Message-State: AOJu0Yw0nCCfvj7UhvzYxrBQpCAOr4Mdc/Ni4UPqv4Tt+gTtmOKEaZoi 8yDDxc9QkSYrklsTpkjc7y2VQSZSZ/3XtzfBxNfBWi+eexbywfYgaA6b X-Gm-Gg: ASbGncvV/GkqEAhxzRkd42NbZY6IoODAZQ6AsWd1rEqFgxz1p4lt+r2+dutlHB3+14+ fVVHxQBYdRLARey99Tyf4h4xSd0Ex/BWWnqV+NLUwJ8yxH1UYNCXHv/VrtV3BWxhmCErNeyzLXG 7PM7fAaokSnwJHGhi7Cc4AXkFjaKS7j+g0+6+41Qg2q3lFpF2gyjjcES6L79nSg5fj3inHGj7dg i08HF7sim7ZsTFZ0YvlLox0cAj9I6jZ+8nLJ6gre1CcVBnI89idxmjMgxnDjuBzA7/nt46D932Z +Ndcat0RZyh+6MRobOPl/tDvVUHVTpvUxJ+0YCLtrYzjYsCANnfRhjxmWm1RrX201bNOvfxZ3Oa k6No56Y5Z9QbCjyWRfPvbcjnoeNFNhhlx6NTPgVxh3vUedDMnKHsaWajsI4zIMM7SRQ3VgrBBjK WgiO8= X-Google-Smtp-Source: AGHT+IGNZAIhfyFqBLRP/OaGacJfil2cCmOQ1auoboLhtrKoumNUaoJC9quip29oNZ5/Ym+GcaL/rQ== X-Received: by 2002:a05:620a:4146:b0:7e6:9993:b34e with SMTP id af79cd13be357-7ea069ad275mr24027185a.33.1755707489000; Wed, 20 Aug 2025 09:31:29 -0700 (PDT) Received: from localhost (modemcable197.17-162-184.mc.videotron.ca. [184.162.17.197]) by smtp.gmail.com with ESMTPSA id d75a77b69052e-4b11de0d731sm84809891cf.40.2025.08.20.09.31.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Aug 2025 09:31:28 -0700 (PDT) From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Lessard?= To: Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley Cc: Geert Uytterhoeven , devicetree@vger.kernel.org, linux-leds@vger.kernel.org, linux-kernel@vger.kernel.org, =?UTF-8?q?Andreas=20F=C3=A4rber?= , Boris Gjenero , Christian Hewitt , Heiner Kallweit , Paolo Sabatino , Martin Blumenstingl Subject: [PATCH v3 4/4] MAINTAINERS: Add entry for TM16xx driver Date: Wed, 20 Aug 2025 12:31:17 -0400 Message-ID: <20250820163120.24997-5-jefflessard3@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20250820163120.24997-1-jefflessard3@gmail.com> References: <20250820163120.24997-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 Signed-off-by: Jean-Fran=C3=A7ois Lessard --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index daf520a13..55afed22f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25402,6 +25402,12 @@ 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 +F: drivers/auxdisplay/tm16xx.c + TMIO/SDHI MMC DRIVER M: Wolfram Sang L: linux-mmc@vger.kernel.org --=20 2.43.0