Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S964956AbdCWOVt (ORCPT ); Thu, 23 Mar 2017 10:21:49 -0400 Received: from mail-cys01nam02on0063.outbound.protection.outlook.com ([104.47.37.63]:14169 "EHLO NAM02-CY1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1755998AbdCWOVo (ORCPT ); Thu, 23 Mar 2017 10:21:44 -0400 Authentication-Results: spf=pass (sender IP is 137.71.25.57) smtp.mailfrom=analog.com; vger.kernel.org; dkim=none (message not signed) header.d=none;vger.kernel.org; dmarc=bestguesspass action=none header.from=analog.com; From: To: , , , , CC: , , , , "Michael Hennerich" Subject: [PATCH] i2c/muxes/i2c-mux-ltc4306: LTC4306 and LTC4305 I2C multiplexer/switch Date: Thu, 23 Mar 2017 15:22:58 +0100 Message-ID: <1490278978-28357-1-git-send-email-michael.hennerich@analog.com> X-Mailer: git-send-email 2.7.4 MIME-Version: 1.0 Content-Type: text/plain X-EOPAttributedMessage: 0 X-MS-Office365-Filtering-HT: Tenant X-Forefront-Antispam-Report: CIP:137.71.25.57;IPV:NLI;CTRY:US;EFV:NLI;SFV:NSPM;SFS:(10009020)(6009001)(39410400002)(39450400003)(39860400002)(39840400002)(39850400002)(2980300002)(438002)(37524003)(199003)(189002)(966004)(86152003)(7636002)(38730400002)(8936002)(5660300001)(50986999)(8676002)(356003)(86362001)(50226002)(305945005)(2876002)(575784001)(2906002)(5003940100001)(6666003)(54906002)(47776003)(189998001)(6306002)(4326008)(77096006)(107886003)(106466001)(50466002)(36756003)(33646002)(1720100001)(230783001)(48376002)(562404015);DIR:OUT;SFP:1101;SCL:1;SRVR:CH1PR03MB1948;H:nwd2mta2.analog.com;FPR:;SPF:Pass;MLV:sfv;A:1;MX:1;LANG:en; X-Microsoft-Exchange-Diagnostics: 1;BY2FFO11FD005;1:Lslk+74avwkQJ+tAr2tsR1R3pbprEtKize1wM09Ew9FJJLXx82kUSzXywN7RcX0EU/hWBRa7m2JNdCZYXvzOeDkR56UGVIA3G+1FuD3auB3SDCSc5ItM7HtYrsIpi7Sy30HDvYFYGKwPrQg++Sd5UdZsq5/FIEjjRJjgMqKRTExWQMunzbpbS+wIyP6Aff/JnoLf4cI55uzjPMpcvl6TPEvi8V4zn556cIbsvhZ7LHnKeMNoee4T3yEfkb8f+wEznziOYcx+ZimDTHhD0E2t10tPhzI5XXWM5Zvu+KG+lHfkymjMvbwbIxd9FgEPMGkePFG84zjtPrqROALVgihn7lWqo4LNCRvRy/6kvekbo8QwY5f+eLImkwv3zGae/zNqvK3zwydnsZ9LQFHPQJMSjdO+OGi6hD2uITGNyLyVwrZFts11EDOBzXQbyh4j/tOg03id1dr1+0U1nbtb9HcPwcZyc3VbKF7WgXcyZogJq8X1XVJ0LvRER22GkkeaNvDpE18ULlPSICTcO6fTK37WAYcA/bGgd+iGuOUhSEcZmYGyZxKiUBv8xehT38L3yMnGP0ni8rY4Owo9hNk2XPVAV9Np9YunR3DwLAkmS9R2wxc= X-MS-Office365-Filtering-Correlation-Id: 24930cb3-1cf0-43f6-eaba-08d471f7ec5e X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(22001)(8251501002)(2017030254075);SRVR:CH1PR03MB1948; X-Microsoft-Exchange-Diagnostics: 1;CH1PR03MB1948;3:aTrfhqwFk35EueXhc9L4GkIiAxC5jjNnzBEu6QebiY8hO630aSBGV0wRCt8WWhwbUkiay5FzUJbGatz7pGi9wxMBs8qY4FkB+hR1KU0JDffmmJYx1wkgFzGOoW0CogrIAUKL8dpX3VLGRNxwJo1g5WIFzryR7bpk95A+mNFZsbOC3oOfvrkv1ikx/xOweLhf0W4/WFYxTHL2FWPLcTITfOLPMkXJF5zYiaaLxb8gzM+41Fhb0cnLn719/+Tem3LoDnnwzu8hdx3NjI7ucgf+Obn8gf/0CLiS6IrvgI47VSmg6oeMJSVt6ztunx76IEx+JQloNkk8OeZvpmbWJSsyMYZ3o8gZ1DBO9qiJ6Eoc5RTN3HCnVMfMHzkpHDmbE48v9ndxTHrQ0Qrzf8Cie2v5LQyMUbpZJ4XUmEI4vXHSyfI=;25:8XxEo5DSsz6xINuxL0NQYKF39RpEnaMmv9bLPW/H5I9/AcGLFESKMbkhhkZ5YDt2M7xPI/wYOSKctmafvsgCEatgy9EN/OKFXldPtWjG0QkgKLekZ5ytIr66EVt0i9Vy1SolInkxw7VCPlaSV2/oBKokbYxJAywkTWN0X0t4lDydxFs+ONLEPxb6+VoAJhDSpOXg9+RmhJ98rEg1lvSSLJFmS2bZpAvXaS2ScNJ9wbaShEWOBX0/aeg9yxB/q9zEsmEtlKhzc4akeG68zyiZWbdT8FPIQtxQ9xz5tQNGKIjJsVzyI4/87lpEqRmQQgVgA53oING9/Wq7hlQp0Px+HX8ncpY5yruXJIFRcV7J8+SG0MBTvyAEw2Dgw40Cd/fv8L6e3K/SuVCL08WafXnb5xrIK/wsmY/efknvdZu1HtjKv8EyU6BS6aXUzOJgNlRB/+WcU46XCaJbDNgjmwJXsw== X-Microsoft-Exchange-Diagnostics: 1;CH1PR03MB1948;31:x8I66w6lXxTLlC1GDWFm/p7ocooqinElEeMs2YY3TdyBuBrvueNu6p5pO47Am7SVWgWBZSMC1UuRLUmFyX3bKBEipCnDAlM81i9Bp2YKtKkCBUtWkoUlS4UjinY2ewsZQBIGgNIjhMEwtKSQMMOkZr67AVs6hCJvxdHdQFxJywuDDGkn+PnDY6XIGNq/tYA7fV0nPpox2+EgoELr7/mUX2QKbmCK0K7RjRYC7lyscYXdWc2TYqlhEzeqwDHTiEG8r8oK6GrGtcWM9myTSJN0pQasmbM5J+dnF06YldD73P0=;20:Wk0MkMMECGbHNKU7ZN4qri/0JTrYXy7+gWTGfnqQk6c8u+7O95L1oomtHSn/CA/kP/xxkO2+AfkHCNzRASx3VbbMbj8sdmgEVe4KXl8/ZqvXWN5seYJIuN3L01CMu0qRTzYizfamCLlVGw5IsyIFtoFkEuKYD2VWhxUx5RrEsPrkUQheBs1cLiyhqMnoLmAl5zP4wq5LUv23vf3Iq3Yjoek39RM5qyO/A7Av0CnMYSdI//ck51aAGKh73yrClcCnq5BE0RWWA7E8pksTHXGGtSMdcHUw+8V6G2jYBqKuDlXZ80dGuPhZIpC7P+o0fIwixjypAakvSa7aF7sCZSObWtoYUJldJDvwzW6QitormOMRw9JehVos1CfytfcZO+lRR7xu+D9j3E4e5Z6A8OIdG5czCLS08WpYp/ElhcUaYV3CBmNAmloUJzq9VlcLN9KWMI0bz5duulnDwd89X0/OphmOprJb9nRnbmceEGNhVBhrZ81ikqH6ou0RhAf9V6q5 X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:(9452136761055)(232431446821674)(170811661138872); X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6040375)(601004)(2401047)(5005006)(13015025)(13017025)(13023025)(13018025)(13024025)(8121501046)(10201501046)(3002001)(6055026)(6041248)(20161123564025)(20161123555025)(20161123562025)(20161123558025)(20161123560025)(6072148);SRVR:CH1PR03MB1948;BCL:0;PCL:0;RULEID:;SRVR:CH1PR03MB1948; X-Microsoft-Exchange-Diagnostics: 1;CH1PR03MB1948;4:w0FSkvGHRfpsEPW1XbBItuhYAqK6a+YdIzUH9O+7I4LcBOHjNDWc+QN04V17xe4C3KgoQu13MDQ2g9H+HRTe+wwJasXn0M3GSGIevnkg2ZsfUWDD+BOLQRHKqQMoEZntTKsGgoXmHIoaNwsVOXtc0GqG3cQedjeoQxQn3ofbJ7z+FLJlor8i47sTUIc89ZD2cct9B9Fi3VX/ROFfSaCkDFWdx3w4f/LinbmWgTqjvm+dQ0eFpb6Do+GTUfXTtfJy9PdfJF1FW+cgbWsu7oeDYwIJiVoLSUJfO34E28SomKiaANB5kbMP93NHFopmR+NQEcV2LdMLsMnQGUxC4gbWhR/RDMA+e0G83aepWqfMpYA8nUjOp8pPwiyZYnBLD69M9sUkyVeguJItSLthJu/EJo6qBPX03UufR+A0eRkMnzkNaPMI+VtlmUWySFKorovNBP7xiE4/bHMtBQ3I0zH6I8TWmbjvV0pusfWRDjKs2kQDqzcKQp38N0UFRlaZ2g+pSg3YSbZN0xajfDnYptHP01C4VDAxOOJXm1w1rJED1Hy1gNJb/xQpEn46ypU698x2ffeRSSS4kcfhrT70bA6ZiKWMNPFChIxcZhU1g22bH/pgDxOvhw13F0Lba73AblT8tAkoka9d9ol3WAEOYdOHiYWVplJYQMs/TOVS55oZRSsVme4EvcMvTTWSn2osCyL5dUUZ1QHIhzpudYs2NGrXO/F8Op9EccsUT1SliNHmjmMhZvf6bN1ktIxqDJzNziyyDgRec1OKFgjGwLgJ+0gpbQmbIbvVcb3eAhb2ExG9sslEiHjmT8hecYLmHn8BVrhVrj7lxmel2ZqEDvhDvIMdpA== X-Forefront-PRVS: 0255DF69B9 X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;CH1PR03MB1948;23:ArJp/V2PXVr+1Z6Y13eiiw/ojTkxHUFB6GUVDvwa+?= =?us-ascii?Q?bm91RifERPXy5eDFMFASsNgyGPsbbxTBr4bbqOg7MY45ItmCm3WNY1FuVUmz?= =?us-ascii?Q?YDQIt/wSUG/zLTtwgMXe9umTYi6q7fLMYqEadmNyjbIfP91EpLUYfey8JHOo?= =?us-ascii?Q?TDtemvCDVDV74D+USJeURewcKxrsj62CqkuwKXfQX1FJ/qXEl4SmuyFAviWK?= =?us-ascii?Q?42SdvehEdej5vPT6Y9YztQfpkyLcxW+JjfAyBy4VejKnmp2GNKjWR8n6tZ5J?= =?us-ascii?Q?O5bqDuPbqNhNx6l6CrLErwLVwxGJhRKiI1Qz/W06L0hwjnefnh19p8XfF6R9?= =?us-ascii?Q?ogZmhL23/0vDbiIhp15H1nRF5tM+BNnl4B4PA5d9NeH63oRsyWtQT1COjPcD?= =?us-ascii?Q?OxysnIbN8H3qsIXB7Ort8s/vjyrniSivPEi2N2P2UdVE7srJycYsNvTOBMDA?= =?us-ascii?Q?qLTL2O4FCdAnO9rSKHq7PnDtDtFTOwiY1GDb2JDYD57FZNiUzwHL/Dcvn/i0?= =?us-ascii?Q?p+4HYcSPfjTVjKUbIDp5EzULZ6u14YE57jRvTtuYz3WLKnEyuju7Fq+sPC0J?= =?us-ascii?Q?XKmytD1dO05dmfXO4v+J8TR7OZ975rCPyJS3GzQpbI8AlAO802M77u/zzony?= =?us-ascii?Q?kfpqCFpP2lpPLt1ttYQYhCEN2UEglwpfv9w7r39HuRnyaVmmlpOn+xrVqZwz?= =?us-ascii?Q?bwRK5SY4UGsiWZfgGH1LCmYDgD0zTwbyDU56vPqQDA5Orz9hIEPkm8Pjs+6/?= =?us-ascii?Q?Q6BkowOlUUkTKpMHWXgMHkeqay77PflsJ0b7m9kubsewukk+GT89BVCaFR0U?= =?us-ascii?Q?CY2cuFYfrKbE862dnsfGLxLUE7/ReRzj4qdE+60yUrGGyDOYdV1w3FMyBwZ1?= =?us-ascii?Q?ZXENj+Q1NNeByQuBoCa9I8W7+WUITUn7n6chsQ3fLrCl/eIuntlK3+voRINO?= =?us-ascii?Q?XYr4xzJaD5+iVlX+dBNMVvBK0Hdxcncgu+wMHr1/vmgHXDJbRt7ucXc6F8rZ?= =?us-ascii?Q?fhsnMrhQ9Mu8BPGYYIYkfxVVaniqGp5spDqUVJnNdsUigNKLYk2B6GJh73Yg?= =?us-ascii?Q?VG9xlzeEFD1YjFQtThapDqM8rLf?= X-Microsoft-Exchange-Diagnostics: 1;CH1PR03MB1948;6:7yb+3dqtdTRGADR/DNobIWQYKwVfSw2zCtWs00lRNOyDZuy0emw27HAWA3x6LPQXThUGFeEW4hyK2QHcAavIrn1AUtLFoRZREnXO9gM/5fK6uFimdPIFiMa1B39MKLj1xgGj1Y5g/ynLUorP7Ts3JVNSnk/K9slKfn0tb2h38wtlXLpWI5d+UgvbQXTeEw08Q2VZiyRfa3zi36d0KPsfZCm45JP0OMbfTVVUzaWMIA4lgzfkb14yh/kPi+d1FoRJk4h2HNdKvOhlGNiw0Q8vJjgDYDgUTGg7qG9eDmfcATWs9PwhEy5kMGa8fc1s0Jq7iupN448UxD+9vi4yGwdapUNNwveAr1OlrMz3juUxgJzmopv8pOv73ZnYAWvhQwHVObziQxGztEPua7chyEP6uGWgDvHQdL8vCe/3tmaBtGQ=;5:Vc0ZYXnhZ3BnHRq/I9gerhf9SDcw8d51vwP9olb00zoMg6ZxIh6D07MKML3V9kh+rcCmAkMChOuWvCwWFmt6Gw7/sjaprUwxumHKOOqtrI1HCovigF5lKNR175iLSVp8d6WRpueb++JCJHZtMNY63A==;24:JpO29epWjsL6pGdTSgi6AoaNhcjaM0z0WoOCV4xu5LzPIHC0ry3zwt/V1/DFRLSB69lVYtb2fTc0Rfiny0hYpAgvRn/W7nlBvmqb0DpWdkE= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;CH1PR03MB1948;7:KtTHhyBJcnT/hG0jgJangfztbmgYWy0+Bjm/Vv++2LkcPEdvkiWV2FMzrlJvfW1Te0V7H6FsqIB/lg/D95UruGZigCHjBzO403Ekhll5pLJiXniYVmaxM9Ht2OUanS3002lfaqFGTdFBMJGF9/2V2GrCFnmZu3Jh33doJn+6p2hNpAboHAWs61VldvNRhnrqd3J5dzfjwegPGqONzr5eKKRDtHvh8aJwk/vAnJZfGByIhNjaTs/DyrY/Yx97au/956e5P3yGK29AcMfsVvGohoFMIEPqySblrvVtH0g7nNhlQz2TYCAAlvICdF3dspY+IQ6+l6F6L0kyLQ4tfmn73g== X-OriginatorOrg: analog.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 23 Mar 2017 14:21:39.9778 (UTC) X-MS-Exchange-CrossTenant-Id: eaa689b4-8f87-40e0-9c6f-7228de4d754a X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=eaa689b4-8f87-40e0-9c6f-7228de4d754a;Ip=[137.71.25.57];Helo=[nwd2mta2.analog.com] X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: CH1PR03MB1948 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 14318 Lines: 511 From: Michael Hennerich This patch adds support for the Analog Devices / Linear Technology LTC4306 and LTC4305 4/2 Channel I2C Bus Multiplexer/Switches. The LTC4306 optionally provides two general purpose input/output pins (GPIOs) that can be configured as logic inputs, opendrain outputs or push-pull outputs via the generic GPIOLIB framework. Signed-off-by: Michael Hennerich --- .../devicetree/bindings/i2c/i2c-mux-ltc4306.txt | 61 ++++ MAINTAINERS | 8 + drivers/i2c/muxes/Kconfig | 10 + drivers/i2c/muxes/Makefile | 1 + drivers/i2c/muxes/i2c-mux-ltc4306.c | 365 +++++++++++++++++++++ 5 files changed, 445 insertions(+) create mode 100644 Documentation/devicetree/bindings/i2c/i2c-mux-ltc4306.txt create mode 100644 drivers/i2c/muxes/i2c-mux-ltc4306.c diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-ltc4306.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-ltc4306.txt new file mode 100644 index 0000000..1e98c6b --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-mux-ltc4306.txt @@ -0,0 +1,61 @@ +* Linear Technology / Analog Devices I2C bus switch + +Required Properties: + + - compatible: Must contain one of the following. + "lltc,ltc4305", "lltc,ltc4306" + - reg: The I2C address of the device. + + The following required properties are defined externally: + + - Standard I2C mux properties. See i2c-mux.txt in this directory. + - I2C child bus nodes. See i2c-mux.txt in this directory. + +Optional Properties: + + - enable-gpios: Reference to the GPIO connected to the enable input. + - i2c-mux-idle-disconnect: Boolean; if defined, forces mux to disconnect all + children in idle state. This is necessary for example, if there are several + multiplexers on the bus and the devices behind them use same I2C addresses. + - gpio-controller: Marks the device node as a GPIO Controller. + - #gpio-cells: Should be two. The first cell is the pin number and + the second cell is used to specify flags. + See ../gpio/gpio.txt for more information. + - ltc,downstream-accelerators-enable: Enables the rise time accelerators + on the downstream port. + - ltc,upstream-accelerators-enable: Enables the rise time accelerators + on the upstream port. + +Example: + + ltc4306: i2c-mux@4a { + compatible = "lltc,ltc4306"; + #address-cells = <1>; + #size-cells = <0>; + reg = <0x4a>; + + gpio-controller; + #gpio-cells = <2>; + + i2c@0 { + #address-cells = <1>; + #size-cells = <0>; + reg = <0>; + + eeprom@50 { + compatible = "at,24c02"; + reg = <0x50>; + }; + }; + + i2c@1 { + #address-cells = <1>; + #size-cells = <0>; + reg = <1>; + + eeprom@50 { + compatible = "at,24c02"; + reg = <0x50>; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index c776906..9a27a19 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7698,6 +7698,14 @@ S: Maintained F: Documentation/hwmon/ltc4261 F: drivers/hwmon/ltc4261.c +LTC4306 I2C MULTIPLEXER DRIVER +M: Michael Hennerich +W: http://ez.analog.com/community/linux-device-drivers +L: linux-i2c@vger.kernel.org +S: Supported +F: drivers/i2c/muxes/i2c-mux-ltc4306.c +F: Documentation/devicetree/bindings/i2c/i2c-mux-ltc4306.txt + LTP (Linux Test Project) M: Mike Frysinger M: Cyril Hrubis diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig index 10b3d17..f501b3b 100644 --- a/drivers/i2c/muxes/Kconfig +++ b/drivers/i2c/muxes/Kconfig @@ -30,6 +30,16 @@ config I2C_MUX_GPIO This driver can also be built as a module. If so, the module will be called i2c-mux-gpio. +config I2C_MUX_LTC4306 + tristate "LTC LTC4306/5 I2C multiplexer" + select GPIOLIB + help + If you say yes here you get support for the LTC LTC4306 or LTC4305 + I2C mux/switch devices. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-ltc4306. + config I2C_MUX_PCA9541 tristate "NXP PCA9541 I2C Master Selector" help diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile index 9948fa4..a452d09 100644 --- a/drivers/i2c/muxes/Makefile +++ b/drivers/i2c/muxes/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o obj-$(CONFIG_I2C_MUX_MLXCPLD) += i2c-mux-mlxcpld.o +obj-$(CONFIG_I2C_MUX_LTC4306) += i2c-mux-ltc4306.o obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o diff --git a/drivers/i2c/muxes/i2c-mux-ltc4306.c b/drivers/i2c/muxes/i2c-mux-ltc4306.c new file mode 100644 index 0000000..f0fd4d1 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-ltc4306.c @@ -0,0 +1,365 @@ +/* + * Linear Technology LTC4306 and LTC4305 I2C multiplexer/switch + * + * Copyright (C) 2017 Analog Devices Inc. + * + * Licensed under the GPL-2. + * + * Based on: i2c-mux-pca954x.c + * + * Datasheet: http://cds.linear.com/docs/en/datasheet/4306.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LTC4306_MAX_NCHANS 4 + +#define LTC_REG_STATUS 0x0 +#define LTC_REG_CONFIG 0x1 +#define LTC_REG_MODE 0x2 +#define LTC_REG_SWITCH 0x3 + +#define LTC_DOWNSTREAM_ACCL_EN BIT(6) +#define LTC_UPSTREAM_ACCL_EN BIT(7) + +#define LTC_GPIO_ALL_INPUT 0xC0 + +enum ltc_type { + ltc_4305, + ltc_4306, +}; + +struct chip_desc { + u8 nchans; + u8 num_gpios; +}; + +struct ltc4306 { + struct i2c_client *client; + struct gpio_chip gpiochip; + const struct chip_desc *chip; + + u8 deselect; + u8 regs[LTC_REG_SWITCH + 1]; +}; + +/* Provide specs for the PCA954x types we know about */ +static const struct chip_desc chips[] = { + [ltc_4305] = { + .nchans = 2, + }, + [ltc_4306] = { + .nchans = LTC4306_MAX_NCHANS, + .num_gpios = 2, + }, +}; + +static int ltc4306_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + int ret = 0; + + if (gpiochip_line_is_open_drain(chip, offset) || + (data->regs[LTC_REG_MODE] & BIT(7 - offset))) { + /* Open Drain or Input */ + ret = i2c_smbus_read_byte_data(data->client, LTC_REG_CONFIG); + if (ret < 0) + return ret; + + return !!(ret & BIT(1 - offset)); + } else { + return !!(data->regs[LTC_REG_CONFIG] & BIT(5 - offset)); + } +} + +static void ltc4306_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + if (value) + data->regs[LTC_REG_CONFIG] |= BIT(5 - offset); + else + data->regs[LTC_REG_CONFIG] &= ~BIT(5 - offset); + + + i2c_smbus_write_byte_data(data->client, LTC_REG_CONFIG, + data->regs[LTC_REG_CONFIG]); +} + +static int ltc4306_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + data->regs[LTC_REG_MODE] |= BIT(7 - offset); + + return i2c_smbus_write_byte_data(data->client, LTC_REG_MODE, + data->regs[LTC_REG_MODE]); +} + +static int ltc4306_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + ltc4306_gpio_set(chip, offset, value); + data->regs[LTC_REG_MODE] &= ~BIT(7 - offset); + + return i2c_smbus_write_byte_data(data->client, LTC_REG_MODE, + data->regs[LTC_REG_MODE]); +} + +static int ltc4306_gpio_set_config(struct gpio_chip *chip, + unsigned int offset, unsigned long config) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + data->regs[LTC_REG_MODE] &= ~BIT(4 - offset); + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + data->regs[LTC_REG_MODE] |= BIT(4 - offset); + break; + default: + return -ENOTSUPP; + } + + return i2c_smbus_write_byte_data(data->client, LTC_REG_MODE, + data->regs[LTC_REG_MODE]); +} + +static int ltc4306_gpio_init(struct ltc4306 *data) +{ + if (!data->chip->num_gpios) + return 0; + + data->gpiochip.label = dev_name(&data->client->dev); + data->gpiochip.base = -1; + data->gpiochip.ngpio = data->chip->num_gpios; + data->gpiochip.parent = &data->client->dev; + data->gpiochip.can_sleep = true; + data->gpiochip.direction_input = ltc4306_gpio_direction_input; + data->gpiochip.direction_output = ltc4306_gpio_direction_output; + data->gpiochip.get = ltc4306_gpio_get; + data->gpiochip.set = ltc4306_gpio_set; + data->gpiochip.set_config = ltc4306_gpio_set_config; + data->gpiochip.owner = THIS_MODULE; + + /* gpiolib assumes all GPIOs default input */ + data->regs[LTC_REG_MODE] |= LTC_GPIO_ALL_INPUT; + i2c_smbus_write_byte_data(data->client, LTC_REG_MODE, + data->regs[LTC_REG_MODE]); + + return devm_gpiochip_add_data(&data->client->dev, + &data->gpiochip, data); +} + +/* + * Write to chip register. Don't use i2c_transfer()/i2c_smbus_xfer() + * as they will try to lock the adapter a second time. + */ +static int ltc4306_reg_write(struct i2c_adapter *adap, + struct i2c_client *client, u8 reg, u8 val) +{ + int ret; + + if (adap->algo->master_xfer) { + struct i2c_msg msg; + char buf[2]; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + buf[0] = reg; + buf[1] = val; + msg.buf = buf; + ret = __i2c_transfer(adap, &msg, 1); + } else { + union i2c_smbus_data data; + + data.byte = val; + ret = adap->algo->smbus_xfer(adap, client->addr, + client->flags, + I2C_SMBUS_WRITE, + reg, + I2C_SMBUS_BYTE_DATA, &data); + } + + return ret; +} + +static int ltc4306_select_chan(struct i2c_mux_core *muxc, u32 chan) +{ + struct ltc4306 *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + u8 regval; + int ret = 0; + + regval = BIT(7 - chan); + + /* Only select the channel if its different from the last channel */ + if (data->regs[LTC_REG_SWITCH] != regval) { + ret = ltc4306_reg_write(muxc->parent, client, + LTC_REG_SWITCH, regval); + data->regs[LTC_REG_SWITCH] = ret < 0 ? 0 : regval; + } + + return ret; +} + +static int ltc4306_deselect_mux(struct i2c_mux_core *muxc, u32 chan) +{ + struct ltc4306 *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + + if (!(data->deselect & BIT(chan))) + return 0; + + /* Deselect active channel */ + data->regs[LTC_REG_SWITCH] = 0; + + return ltc4306_reg_write(muxc->parent, client, + LTC_REG_SWITCH, data->regs[LTC_REG_SWITCH]); +} + +static const struct i2c_device_id ltc4306_id[] = { + { "ltc4305", ltc_4305 }, + { "ltc4306", ltc_4306 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4306_id); + +static const struct of_device_id ltc4306_of_match[] = { + { .compatible = "lltc,ltc4305", .data = &chips[ltc_4305] }, + { .compatible = "lltc,ltc4306", .data = &chips[ltc_4306] }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc4306_of_match); + +static int ltc4306_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); + struct device_node *of_node = client->dev.of_node; + bool idle_disconnect_dt = false; + struct gpio_desc *gpio; + struct i2c_mux_core *muxc; + struct ltc4306 *data; + const struct of_device_id *match; + int num, ret; + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + muxc = i2c_mux_alloc(adap, &client->dev, + LTC4306_MAX_NCHANS, sizeof(*data), 0, + ltc4306_select_chan, ltc4306_deselect_mux); + if (!muxc) + return -ENOMEM; + data = i2c_mux_priv(muxc); + + i2c_set_clientdata(client, muxc); + data->client = client; + + /* Enable the mux if a enable GPIO is specified. */ + gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + /* Write the mux register at addr to verify + * that the mux is in fact present. This also + * initializes the mux to disconnected state. + */ + if (i2c_smbus_write_byte_data(client, LTC_REG_SWITCH, 0) < 0) { + dev_warn(&client->dev, "probe failed\n"); + return -ENODEV; + } + + match = of_match_device(of_match_ptr(ltc4306_of_match), &client->dev); + if (match) + data->chip = of_device_get_match_data(&client->dev); + else + data->chip = &chips[id->driver_data]; + + if (of_node) { + idle_disconnect_dt = + of_property_read_bool(of_node, + "i2c-mux-idle-disconnect"); + if (of_property_read_bool(of_node, + "ltc,downstream-accelerators-enable")) + data->regs[LTC_REG_CONFIG] |= LTC_DOWNSTREAM_ACCL_EN; + + if (of_property_read_bool(of_node, + "ltc,upstream-accelerators-enable")) + data->regs[LTC_REG_CONFIG] |= LTC_UPSTREAM_ACCL_EN; + + if (i2c_smbus_write_byte_data(client, LTC_REG_CONFIG, + data->regs[LTC_REG_CONFIG]) < 0) { + dev_warn(&client->dev, "probe failed\n"); + return -ENODEV; + } + } + + ret = ltc4306_gpio_init(data); + if (ret < 0) + return ret; + + /* Now create an adapter for each channel */ + for (num = 0; num < data->chip->nchans; num++) { + + data->deselect |= idle_disconnect_dt << num; + + ret = i2c_mux_add_adapter(muxc, 0, num, 0); + if (ret) { + dev_err(&client->dev, + "failed to register multiplexed adapter %d\n", + num); + goto virt_reg_failed; + } + } + + dev_info(&client->dev, + "registered %d multiplexed busses for I2C switch %s\n", + num, client->name); + + return 0; + +virt_reg_failed: + i2c_mux_del_adapters(muxc); + return ret; +} + +static int ltc4306_remove(struct i2c_client *client) +{ + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + + i2c_mux_del_adapters(muxc); + return 0; +} + +static struct i2c_driver ltc4306_driver = { + .driver = { + .name = "ltc4306", + .of_match_table = of_match_ptr(ltc4306_of_match), + }, + .probe = ltc4306_probe, + .remove = ltc4306_remove, + .id_table = ltc4306_id, +}; + +module_i2c_driver(ltc4306_driver); + +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Linear Technology LTC4306, LTC4305 I2C mux/switch driver"); +MODULE_LICENSE("GPL v2"); -- 2.7.4