From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752658AbcKQWXH (ORCPT ); Thu, 17 Nov 2016 17:23:07 -0500 Received: from mail-ve1eur01on0133.outbound.protection.outlook.com ([104.47.1.133]:10311 "EHLO EUR01-VE1-obe.outbound.protection.outlook.com" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1752017AbcKQWXD (ORCPT ); Thu, 17 Nov 2016 17:23:03 -0500 X-Greylist: delayed 72089 seconds by postgrey-1.27 at vger.kernel.org; Thu, 17 Nov 2016 17:23:02 EST Authentication-Results: spf=none (sender IP is ) smtp.mailfrom=peda@axentia.se; From: Peter Rosin To: CC: Peter Rosin , Wolfram Sang , "Rob Herring" , Mark Rutland , "Jonathan Cameron" , Hartmut Knaack , "Lars-Peter Clausen" , Peter Meerwald-Stadler , "Arnd Bergmann" , Greg Kroah-Hartman , , , Subject: [RFC PATCH v2 2/7] misc: minimal mux subsystem and gpio-based mux controller Date: Thu, 17 Nov 2016 22:48:04 +0100 Message-ID: <1479419289-17553-3-git-send-email-peda@axentia.se> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1479419289-17553-1-git-send-email-peda@axentia.se> References: <1479419289-17553-1-git-send-email-peda@axentia.se> MIME-Version: 1.0 Content-Type: text/plain X-Originating-IP: [217.210.101.82] X-ClientProxiedBy: DB5PR03CA0075.eurprd03.prod.outlook.com (10.164.34.43) To DB6PR0201MB2311.eurprd02.prod.outlook.com (10.169.222.150) X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2311;2:K6TpXTp1K4t7WJQNmCp28RvS6Q4/Ko9IfL3cj0RPyw7nn8uT2xjiDm6kDbfXZZFEkFkeCv49oHgM5Obf4KflyQbq90IKkwQa8NJTdX/nISaaPHRkj6KbhQ67I8dTBTG1Oz719fM7aRCPpgE5uOzkCdvgoPUqm/pkmNwYDNEd0JY=;3:jRD9uKYneD5A7ESksCxdCLyn+WIQzDNGuj035x8WvFGFJpvX9C0aui/aR4H1t04dlr8Hr3mPd3ZtIgaeN0VOTjr8KwJg0wUIolNZvKYx7sMoji3oHWlI9795SLP0KoseksQXfdxPBHBzjQXrnpTVShHMNYWs51YxA55U8CJYaNA=;25:DmeEOqHpq7nrZHkgDoraQBlw9JNYeQaFBgR0f5aBaNB5lz7sdXbuR+0ITffCsP+ZTye/aXBiuU3aLBdJYUD77op8PTtqXLrdkqLT0RWZntho29f8wodYEuf5svMvuMiQrpdnZDjU0B9fP0Z+hdrElf0+/eq/RkPfds3MImtsDRxvW35nqhVqMC3221HsCKSTi8oElm6SXQiZ7wNBTPZ3qfh/P1twW9KKIFQGPeXL5xapT89NZZ0Xw3vmJPqW8eE7QtI+GVca505m3dRBBdvUjUpOA8ZYiCmpfg8tmf7RexpqVQi8G8XTuiZVnXahsR3lqwvC+dXrMAijn8UcIj+2krAoXN/Y+N9eJJDclw3kRcj43UQoCnYxSCcUVZaSjSzgKpa+gF4k0RLo0KjThj6HmK6qwkVpBzjDxPulmUZjpktOHVUpWpoAYuNEN4383CYEcvBNZuSZTmEOyrxZpoSKRA== X-MS-Office365-Filtering-Correlation-Id: f1ccbfd2-697a-4311-7503-08d40f33855f X-Microsoft-Antispam: UriScan:;BCL:0;PCL:0;RULEID:(22001);SRVR:DB6PR0201MB2311; X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2311;31:rUq70skplcoBsp5H6QDxzCW7HfKITwQvREA/M2F25Zvlk1Q/zesNSinzu+ApdRMWn+tV0FygLcep8W9MC9cDTMFd8NBhaEWYRljvYnY9euZ3MNW1s5+1JKyJE0qG26YDWvjt4IC53cQm/BFzmRFh6ZP01e4C/THb/KvpaZNHmdjpXOY90yJNCLxbyDi9rA1KuxlkPHD9UDZQXfsE61V0JFAA9j0DTJFUdP/BPuRB+IX3ndVrm3BmG11c5I69MyTTyID4u8pMp/WWkC23CbQ7oA==;4:vmmF4d6e1K60H/tYR4JDU41hKY7Ic8uX4kOfXPsAozxakg8S91Pnn75vtwxKXymswu/R1/1hGtBVNVFbdC+oOuweuMdhJGbC44O9ryqexADQ0vir6+yXBxGZ1+DU/4kjZRkz18KmYnrQYMckwC6c1tR2ZqAwXc9QOo7qBCL36fMWKhlglhn+02T8qw+sZnCqh4d0yfLZ44KtHgiGLOu7DojoXKuuVjTKN4uPBpOurEStg0xisAELqhZeDbUYZWJrINnnCqZ/sGd5VeLPyWLVmJ5aMq9DKPaNIz3LooKnVd6WNyaFdorzZI9/zRIZqb58LfiiQTA0X2vDOcnUuqYyW4k0NRyHTi/7y4AJddXSrXK3PNZJF4ClaJMCK5/Lw7trjya6oLLu2OqFzz60YEORU1z5xI/mPKYqjWImPMKZAscljuVfyU7xThHr1u9BwDSPh8jdmpV6jEdJz2L3BitaM+5JCU9UjWx9g9zRgElOvrw= X-Microsoft-Antispam-PRVS: X-Exchange-Antispam-Report-Test: UriScan:; X-Exchange-Antispam-Report-CFA-Test: BCL:0;PCL:0;RULEID:(6040281)(6060326)(601004)(2401047)(5005006)(8121501046)(3002001)(10201501046)(6061324)(6041223)(6043046);SRVR:DB6PR0201MB2311;BCL:0;PCL:0;RULEID:;SRVR:DB6PR0201MB2311; X-Forefront-PRVS: 01294F875B X-Forefront-Antispam-Report: SFV:NSPM;SFS:(10019020)(4630300001)(6009001)(6069001)(7916002)(189002)(199003)(305945005)(8676002)(101416001)(7416002)(8666005)(7736002)(7846002)(50466002)(48376002)(92566002)(50986999)(76176999)(105586002)(106356001)(2351001)(36756003)(33646002)(42186005)(74482002)(6116002)(3846002)(2906002)(4326007)(81166006)(5003940100001)(81156014)(50226002)(110136003)(5660300001)(6666003)(2950100002)(6916009)(47776003)(66066001)(68736007)(77096005)(86362001)(189998001)(97736004)(7059030)(2004002)(217873001)(42262002);DIR:OUT;SFP:1102;SCL:1;SRVR:DB6PR0201MB2311;H:localhost.localdomain;FPR:;SPF:None;PTR:InfoNoRecords;MX:1;A:1;LANG:en; X-Microsoft-Exchange-Diagnostics: =?us-ascii?Q?1;DB6PR0201MB2311;23:glwjOEgEJ5P5Ah9VLYDmZA1D5QoY8PJXFsgNTQf?= =?us-ascii?Q?ERvKllEl2/imiL1oHUBLTXeGF3LrKYgrslgewJm2Vm0kLCzYytS2qo7t/n2i?= =?us-ascii?Q?8o/I8nJEFxEHaTey/uMxv0M/p/HYauFijvqjMUFHJ1h1DIt0isdN+fmHhLe4?= =?us-ascii?Q?kvfGb0DNHvKVrhbKSlaAjTSAxeqG3XECfJWq6w5T12sNPLHcBprybLt6vfR8?= =?us-ascii?Q?KKdKkHlCBErL4n8JV10qTp15YIrB5R1ucouLEgJnalBIuSkRHmR0Tu5RHGYl?= =?us-ascii?Q?ZOszGcwywlf7/IazrjFAl6hNgwEWydIOIdcIbuOoIbynjqXEhDHOaQp1XfHs?= =?us-ascii?Q?DSljxNfWRfAI+4WLqJ3NqYvdH2nPaVLdE4sLWjBV3cS8aDgToWTbkYd/oemy?= =?us-ascii?Q?Sk6spZeMlNKMc0Vhu7uy90MLgynzuie8Nm+Gma22fQZNWg+UWh+W0Yn8TLmz?= =?us-ascii?Q?LDorACXWAprqX4HL2MmBEtuZQCPQIQEcB9N95OYPrUlVuoZzKftbvE9edrPA?= =?us-ascii?Q?6DqQyVl3Pw3f70YTM7ot+qbrDPeM+A94EBBNOL4vt1zBeGb4PrT/y0A1kmoE?= =?us-ascii?Q?mq8uhvuQAgXVpXHhwb5w9Jax4g8CNJPQ9AlFJ5oDXZ6lbrL7d9jdyCJ6bxxi?= =?us-ascii?Q?vxR7hTR1iET/rn5jSor5zIQ6AQ9QvLyGJ9v2CWhi3vLGDe13oZiElmicYgsm?= =?us-ascii?Q?6j3tk6gkVAaxgY88n8yrH88ECTe5jggVcXvGKvvBYtQ4I/Fnr4ZyYZ9Oy4Qy?= =?us-ascii?Q?iLUvOrOTv4chxfB6NGo+kDaYDD7Gmc5TW0HByoSdJJn9d+UE5ORLaOtAo0ua?= =?us-ascii?Q?JZT+YXkcBwjWHLgTfiWjT8CtE9yR0xyHt3xppN9HsOblQDyPFUMB0H1cCQ0e?= =?us-ascii?Q?m0gFAEtVN7vwAU98j1DmrPGuYAeNkNPerKvq8lhSypeIq1G0lLMQfUAU7EUu?= =?us-ascii?Q?Wz5TE4oVQIoMg/uHkkHeLLJcNPZnikpef2TjwbiDkn939P3hjtTT6kU/MDsh?= =?us-ascii?Q?GKGjWdJ9CewLuCC7ZXpNXK59ZEVZVttOmGbssb/xSZhNsx+Dl1z4r756TKx6?= =?us-ascii?Q?oPvZ3nbyNnqHURRtQsSrL13F5bwhEZhOUXCT3B2Vh7+dYM1UWSsUymB5dZt2?= =?us-ascii?Q?3B1pKRSSjYQC3jAm02R2el0x51Y4EVhc2X2NUTkBaypQnBbukzbMpWA=3D?= =?us-ascii?Q?=3D?= X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2311;6:eAN8+DWAlAKly1hRTqxXDpZPcFIf9BXGe+4GO5vZVQoV3BUNyDywUOHzPa+w5pIVGNB0SqQhuvNFGWxuakSbJZqNlftfBKCnBwCuVH3EjayPR/SrS0pFE3KwJMH+KPLs5fAmBHqY2Dtwb6fJ1OAYZRwWL2wFoW4sG+2ecCYbaiO+Sln15zNhOiUj1UxMpMyJdnYzW/THv5oUh+ZkAsB/fk3afvtUlIKc3wfBgwx+laCg/t5fB6dZhh3xr+UZp0/rPzGP3Vq/H+rPwMfj6+VueZSROfvW3rRR4NE/HOOrwZDSWAinuBX6A8gSijL+ZPpWaGOL5cILL+aZzAmRsmv9YOhfUgEGURvV6YUnc0w9gbs4XMR2YvqSBA35eHxjZR+h;5:JX5y/65KXiNnFnRkiW5GMp0ghseEXdQbdLbF3MP1DOY443i+hKd0N15r6maxcUC4q0jR1stipfv/cPWddRQyj8Zp7ZYYUrDh0aLYu5V2VWIIUYPHyTfhg/u0IMVBFZ7nZJwwI6d006qikFh8sd7wjwUvhDOczpryp0McQUkJxvs=;24:qho1e4/nGe/mJKmYRSWGbSzIB3XCY8+I9sFklMTm22kr8oRb2C/Nar5/AB5AOd7yK6AJBxiorEQXXhSaguf4ONU4EAEaXNVhwXglNTKErsA= SpamDiagnosticOutput: 1:99 SpamDiagnosticMetadata: NSPM X-Microsoft-Exchange-Diagnostics: 1;DB6PR0201MB2311;7:tpsp9x+TYUzG2BwV3nMvXOAhou4OlgKV/mAJ/VCJxEw14kTOYWmuY6AzgGMHFIhmPKVX3WovY+tzIoLWzk+s4M9PAth1GjIC1diS78t0CtPsfnZ7GRgZH6+P+rx5ohUyhCdjVFoN26iTMm1XT0ybtnPz3H76oeCZRqD5rV1Ha4kA5fAeSLHeSSEBqQbHZG9GXLVBEVsC+wqaetHhpfeuOIpp5cP+KOFWC670tGqJtPSSEhmR2b/FrJibK1RN/bW++yWXIiDStgO93HcCPOIjIcjOBhoi1TELVvwS46BJZ5ClS3OXF9edgbjWH2ymz5Tf5vXQuuSYLO0TmK0YoNYRFT2U2ddAUitR8oT17KO0dJI= X-OriginatorOrg: axentia.se X-MS-Exchange-CrossTenant-OriginalArrivalTime: 17 Nov 2016 21:48:50.7244 (UTC) X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted X-MS-Exchange-Transport-CrossTenantHeadersStamped: DB6PR0201MB2311 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org When both the iio subsystem and the i2c subsystem wants to update the same mux, there needs to be some coordination. Invent a new minimal "mux" subsystem that handles this. Add a single backend driver for this new subsystem that can control gpio based multiplexers. --- drivers/misc/Kconfig | 6 + drivers/misc/Makefile | 2 + drivers/misc/mux-core.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ drivers/misc/mux-gpio.c | 115 +++++++++++++++++++ include/linux/mux.h | 53 +++++++++ 5 files changed, 475 insertions(+) create mode 100644 drivers/misc/mux-core.c create mode 100644 drivers/misc/mux-gpio.c create mode 100644 include/linux/mux.h diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 64971baf11fa..9e119bb78d82 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -766,6 +766,12 @@ config PANEL_BOOT_MESSAGE An empty message will only clear the display at driver init time. Any other printf()-formatted message is valid with newline and escape codes. +config MUX_GPIO + tristate "GPIO-controlled MUX controller" + depends on OF + help + GPIO-controlled MUX controller + source "drivers/misc/c2port/Kconfig" source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 31983366090a..92b547bcbac1 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -53,6 +53,8 @@ obj-$(CONFIG_ECHO) += echo/ obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o obj-$(CONFIG_CXL_BASE) += cxl/ obj-$(CONFIG_PANEL) += panel.o +obj-$(CONFIG_MUX_GPIO) += mux-core.o +obj-$(CONFIG_MUX_GPIO) += mux-gpio.o lkdtm-$(CONFIG_LKDTM) += lkdtm_core.o lkdtm-$(CONFIG_LKDTM) += lkdtm_bugs.o diff --git a/drivers/misc/mux-core.c b/drivers/misc/mux-core.c new file mode 100644 index 000000000000..7a8bf003a92c --- /dev/null +++ b/drivers/misc/mux-core.c @@ -0,0 +1,299 @@ +/* + * Multiplexer subsystem + * + * Copyright (C) 2016 Axentia Technologies AB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define pr_fmt(fmt) "mux-core: " fmt + +#include +#include +#include +#include +#include +#include +#include + +static struct bus_type mux_bus_type = { + .name = "mux", +}; + +static int __init mux_init(void) +{ + return bus_register(&mux_bus_type); +} + +static void __exit mux_exit(void) +{ + bus_unregister(&mux_bus_type); +} + +static DEFINE_IDA(mux_ida); + +static void mux_control_release(struct device *dev) +{ + struct mux_control *mux = to_mux_control(dev); + + ida_simple_remove(&mux_ida, mux->id); + kfree(mux); +} + +static struct device_type mux_control_type = { + .name = "mux-control", + .release = mux_control_release, +}; + +/* + * Allocate a mux-control, plus an extra memory area for private use + * by the caller. + */ +struct mux_control *mux_control_alloc(size_t sizeof_priv) +{ + struct mux_control *mux; + + mux = kzalloc(sizeof(*mux) + sizeof_priv, GFP_KERNEL); + if (!mux) + return NULL; + + mux->dev.bus = &mux_bus_type; + mux->dev.type = &mux_control_type; + device_initialize(&mux->dev); + dev_set_drvdata(&mux->dev, mux); + + init_rwsem(&mux->lock); + mux->priv = mux + 1; + + mux->id = ida_simple_get(&mux_ida, 0, 0, GFP_KERNEL); + if (mux->id < 0) { + pr_err("mux-controlX failed to get device id\n"); + kfree(mux); + return NULL; + } + dev_set_name(&mux->dev, "mux:control%d", mux->id); + + mux->cached_state = -1; + mux->idle_state = -1; + + return mux; +} +EXPORT_SYMBOL_GPL(mux_control_alloc); + +/* + * Register the mux-control, thus readying it for use. + */ +int mux_control_register(struct mux_control *mux) +{ + /* If the calling driver did not initialize of_node, do it here */ + if (!mux->dev.of_node && mux->dev.parent) + mux->dev.of_node = mux->dev.parent->of_node; + + return device_add(&mux->dev); +} +EXPORT_SYMBOL_GPL(mux_control_register); + +/* + * Take the mux-control off-line. + */ +void mux_control_unregister(struct mux_control *mux) +{ + device_del(&mux->dev); +} +EXPORT_SYMBOL_GPL(mux_control_unregister); + +/* + * Put away the mux-control for good. + */ +void mux_control_put(struct mux_control *mux) +{ + if (!mux) + return; + put_device(&mux->dev); +} +EXPORT_SYMBOL_GPL(mux_control_put); + +static int mux_control_set(struct mux_control *mux, int state) +{ + int ret = mux->ops->set(mux, state); + + mux->cached_state = ret < 0 ? -1 : state; + + return ret; +} + +/* + * Select the given multiplexer channel. Call mux_control_deselect() + * when the operation is complete on the multiplexer channel, and the + * multiplexer is free for others to use. + */ +int mux_control_select(struct mux_control *mux, int state) +{ + int ret; + + if (down_read_trylock(&mux->lock)) { + if (mux->cached_state == state) + return 0; + + /* Sigh, the mux needs updating... */ + up_read(&mux->lock); + } + + /* ...or it's just contended. */ + down_write(&mux->lock); + + if (mux->cached_state == state) { + /* + * Hmmm, someone else changed the mux to my liking. + * That makes me wonder how long I waited for nothing... + */ + downgrade_write(&mux->lock); + return 0; + } + + ret = mux_control_set(mux, state); + if (ret < 0) { + if (mux->idle_state != -1) + mux_control_set(mux, mux->idle_state); + + up_write(&mux->lock); + return ret; + } + + downgrade_write(&mux->lock); + + return 1; +} +EXPORT_SYMBOL_GPL(mux_control_select); + +/* + * Deselect the previously selected multiplexer channel. + */ +int mux_control_deselect(struct mux_control *mux) +{ + int ret = 0; + + if (mux->idle_state != -1 && mux->cached_state != mux->idle_state) + ret = mux_control_set(mux, mux->idle_state); + + up_read(&mux->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(mux_control_deselect); + +static int of_dev_node_match(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static struct mux_control *of_find_mux_by_node(struct device_node *np) +{ + struct device *dev; + + dev = bus_find_device(&mux_bus_type, NULL, np, of_dev_node_match); + + return dev ? to_mux_control(dev) : NULL; +} + +static struct mux_control *of_mux_control_get(struct device_node *np, int index) +{ + struct device_node *mux_np; + struct mux_control *mux; + + mux_np = of_parse_phandle(np, "control-muxes", index); + if (!mux_np) + return NULL; + + mux = of_find_mux_by_node(mux_np); + of_node_put(mux_np); + + return mux; +} + +/* + * Get a named mux. + */ +struct mux_control *mux_control_get(struct device *dev, const char *mux_name) +{ + struct device_node *np = dev->of_node; + struct mux_control *mux; + int index; + + index = of_property_match_string(np, "control-mux-names", mux_name); + if (index < 0) { + dev_err(dev, "failed to get control-mux %s:%s(%i)\n", + np->full_name, mux_name ?: "", index); + return ERR_PTR(index); + } + + mux = of_mux_control_get(np, index); + if (!mux) + return ERR_PTR(-EPROBE_DEFER); + + return mux; +} +EXPORT_SYMBOL_GPL(mux_control_get); + +static void devm_mux_control_free(struct device *dev, void *res) +{ + struct mux_control *mux = *(struct mux_control **)res; + + mux_control_put(mux); +} + +/* + * Get a named mux, with resource management. + */ +struct mux_control *devm_mux_control_get(struct device *dev, + const char *mux_name) +{ + struct mux_control **ptr, *mux; + + ptr = devres_alloc(devm_mux_control_free, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + mux = mux_control_get(dev, mux_name); + if (IS_ERR(mux)) { + devres_free(ptr); + return mux; + } + + *ptr = mux; + devres_add(dev, ptr); + + return mux; +} +EXPORT_SYMBOL_GPL(devm_mux_control_get); + +static int devm_mux_control_match(struct device *dev, void *res, void *data) +{ + struct mux_control **r = res; + + if (!r || !*r) { + WARN_ON(!r || !*r); + return 0; + } + + return *r == data; +} + +/* + * Resource-managed version mux_control_put. + */ +void devm_mux_control_put(struct device *dev, struct mux_control *mux) +{ + WARN_ON(devres_release(dev, devm_mux_control_free, + devm_mux_control_match, mux)); +} +EXPORT_SYMBOL_GPL(devm_mux_control_put); + +subsys_initcall(mux_init); +module_exit(mux_exit); + +MODULE_AUTHOR("Peter Rosin +#include +#include +#include +#include +#include +#include + +struct mux_gpio { + struct gpio_descs *gpios; +}; + +static int mux_gpio_set(struct mux_control *mux, int val) +{ + struct mux_gpio *mux_gpio = mux->priv; + int i; + + for (i = 0; i < mux_gpio->gpios->ndescs; i++) + gpiod_set_value_cansleep(mux_gpio->gpios->desc[i], + val & (1 << i)); + + return 0; +} + +static const struct mux_control_ops mux_gpio_ops = { + .set = mux_gpio_set, +}; + +static const struct of_device_id mux_gpio_dt_ids[] = { + { .compatible = "mux-gpio", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mux_gpio_dt_ids); + +static int mux_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = pdev->dev.of_node; + struct mux_control *mux; + struct mux_gpio *mux_gpio; + u32 idle_state; + int ret; + + if (!np) + return -ENODEV; + + mux = mux_control_alloc(sizeof(*mux_gpio)); + if (!mux) + return -ENOMEM; + mux_gpio = mux->priv; + mux->dev.parent = dev; + mux->ops = &mux_gpio_ops; + + platform_set_drvdata(pdev, mux); + + mux_gpio->gpios = devm_gpiod_get_array(dev, "mux", GPIOD_OUT_LOW); + if (IS_ERR(mux_gpio->gpios)) { + if (PTR_ERR(mux_gpio->gpios) != -EPROBE_DEFER) + dev_err(dev, "failed to get gpios\n"); + mux_control_put(mux); + return PTR_ERR(mux_gpio->gpios); + } + + ret = of_property_read_u32(np, "idle-state", &idle_state); + if (ret >= 0) { + if (idle_state >= (1 << mux_gpio->gpios->ndescs)) { + dev_err(dev, "invalid idle-state %u\n", idle_state); + return -EINVAL; + } + mux->idle_state = idle_state; + } + + ret = mux_control_register(mux); + if (ret < 0) { + dev_err(dev, "failed to register mux_control\n"); + mux_control_put(mux); + return ret; + } + + return ret; +} + +static int mux_gpio_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mux_control *mux = to_mux_control(dev); + + mux_control_unregister(mux); + mux_control_put(mux); + return 0; +} + +static struct platform_driver mux_gpio_driver = { + .driver = { + .name = "mux-gpio", + .of_match_table = of_match_ptr(mux_gpio_dt_ids), + }, + .probe = mux_gpio_probe, + .remove = mux_gpio_remove, +}; +module_platform_driver(mux_gpio_driver); + +MODULE_AUTHOR("Peter Rosin +#include + +struct mux_control; + +struct mux_control_ops { + int (*set)(struct mux_control *mux, int reg); +}; + +struct mux_control { + struct rw_semaphore lock; /* protects the state of the mux */ + + struct device dev; + int id; + + int cached_state; + int idle_state; + + const struct mux_control_ops *ops; + + void *priv; +}; + +#define to_mux_control(x) container_of((x), struct mux_control, dev) + +struct mux_control *mux_control_alloc(size_t sizeof_priv); +int mux_control_register(struct mux_control *mux); +void mux_control_unregister(struct mux_control *mux); +void mux_control_put(struct mux_control *mux); + +int mux_control_select(struct mux_control *mux, int state); +int mux_control_deselect(struct mux_control *mux); + +struct mux_control *mux_control_get(struct device *dev, + const char *mux_name); +struct mux_control *devm_mux_control_get(struct device *dev, + const char *mux_name); +void devm_mux_control_put(struct device *dev, struct mux_control *mux); + +#endif /* _LINUX_MUX_H */ -- 2.1.4