From nobody Mon Feb 9 17:57:29 2026 Received: from layka.disroot.org (layka.disroot.org [178.21.23.139]) (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 C39A431A7FF; Thu, 13 Nov 2025 19:08:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=178.21.23.139 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763060900; cv=none; b=V4GHZKmXnUKRpF96xocc85ON4T8xLe0xBBJOBm6QvPtb8uhZGoMG8Cnc89EHhRu6Sr670s1SpRdB38cnl0eMqNy+I8O8IOFZl4+jS2Z8ph8Uy0ziLvLm9SX6COwPIlHk5yFvRF/8lJXRJKL4gv9I1VZlN9ROrODCbHFLKjmkE/w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763060900; c=relaxed/simple; bh=TpT1IKFG1YYJ6rXNq74OVbWAud79/Inf1eMbHTQeemk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=s8ELaB0Ui6j5EGRhVC9AgMK7qH12o57y8Ob8OmKIt3DrKGmnJwqYprHFxF9mQ7oD/SuEJ99srFC/SfmEteeDK8S8oaO6t5a4oTQUSFZ6x3tWYN6UY2rzskj3mYOLg21qg2AXEgHWOqLZN+8EqmQ/QOclJi4a/H5Vv3Ivckim66A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=disroot.org; spf=pass smtp.mailfrom=disroot.org; dkim=pass (2048-bit key) header.d=disroot.org header.i=@disroot.org header.b=R7CP+25g; arc=none smtp.client-ip=178.21.23.139 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=disroot.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=disroot.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=disroot.org header.i=@disroot.org header.b="R7CP+25g" Received: from mail01.disroot.lan (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id 4A16126723; Thu, 13 Nov 2025 20:08:15 +0100 (CET) X-Virus-Scanned: SPAM Filter at disroot.org Received: from layka.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id kLkA_Opwh9vF; Thu, 13 Nov 2025 20:08:14 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1763060894; bh=TpT1IKFG1YYJ6rXNq74OVbWAud79/Inf1eMbHTQeemk=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=R7CP+25gqfecXbbQ0+VDPTuSw5huEYlEVpMNeBHs5A2Z3RPdJcqzBOEvUb69PFRtn Y7zWhalZ9Bu/A4lw3Z4/QDrijo+sBGRAtyjmLW+MgtOiuJc3P6eN1ePIw3RDd+x46/ zukPkdruSrfurd83s04Bx3c3ulqWIRZE+EL1M/+YFk7Lz2oggivyp3d71dAjn5d0p9 HrM7zW7qJjFzx7taiNeJm4mxY9YeG6KdVTmqpufwJ7UwF04PygVo6yJr70kDBLTtb3 zfbetmM5DK1XmCWEFKD32/LJEUzvyHV0A1/28ECXQNIC5nD5+JUWYaWr09vmveDtYe 7azQLaZIB2L8g== From: Kaustabh Chakraborty Date: Fri, 14 Nov 2025 00:35:13 +0530 Subject: [PATCH 12/13] extcon: add support for Samsung S2M series PMIC extcon devices 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 Message-Id: <20251114-s2mu005-pmic-v1-12-9e3184d3a0c9@disroot.org> References: <20251114-s2mu005-pmic-v1-0-9e3184d3a0c9@disroot.org> In-Reply-To: <20251114-s2mu005-pmic-v1-0-9e3184d3a0c9@disroot.org> To: Lee Jones , Pavel Machek , Rob Herring , Krzysztof Kozlowski , Conor Dooley , MyungJoo Ham , Chanwoo Choi , Sebastian Reichel , Krzysztof Kozlowski , =?utf-8?q?Andr=C3=A9_Draszik?= , Alexandre Belloni , Jonathan Corbet Cc: linux-leds@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, linux-samsung-soc@vger.kernel.org, linux-rtc@vger.kernel.org, linux-doc@vger.kernel.org, Kaustabh Chakraborty X-Developer-Signature: v=1; a=ed25519-sha256; t=1763060713; l=12449; i=kauschluss@disroot.org; s=20250202; h=from:subject:message-id; bh=TpT1IKFG1YYJ6rXNq74OVbWAud79/Inf1eMbHTQeemk=; b=9RAtfupnnemdrIOOualRnhVRkMTkMB/MqMtGR2bWnU/Upw7TaCWtE55BBVgLzifsJs4KWYjOn VkBwBq4sd3OBo5rN2yobgURcIEVWdUJ3APrnMPLupwasP2WXxansECl X-Developer-Key: i=kauschluss@disroot.org; a=ed25519; pk=h2xeR+V2I1+GrfDPAhZa3M+NWA0Cnbdkkq1bH3ct1hE= Add a driver for MUIC devices found in certain Samsung S2M series PMICs These are USB port accessory detectors. These devices report multiple cable states depending on the ID-GND resistance measured by an internal ADC. The driver includes initial support for the S2MU005 PMIC extcon. Signed-off-by: Kaustabh Chakraborty --- drivers/extcon/Kconfig | 10 ++ drivers/extcon/Makefile | 1 + drivers/extcon/extcon-s2m.c | 355 ++++++++++++++++++++++++++++++++++++++++= ++++ 3 files changed, 366 insertions(+) diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig index aec46bf03302..89b3427175f7 100644 --- a/drivers/extcon/Kconfig +++ b/drivers/extcon/Kconfig @@ -182,6 +182,16 @@ config EXTCON_RT8973A and switch that is optimized to protect low voltage system from abnormal high input voltage (up to 28V). =20 +config EXTCON_S2M + tristate "Samsung S2M series PMIC EXTCON support" + depends on MFD_SEC_CORE + select REGMAP_IRQ + help + This option enables support for MUIC devices found in certain + Samsung S2M series PMICs, such as the S2MU005. These devices + have internal ADCs measuring the ID-GND resistance, thereby + can be used as a USB port accessory detector. + config EXTCON_SM5502 tristate "Silicon Mitus SM5502/SM5504/SM5703 EXTCON support" depends on I2C diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile index 6482f2bfd661..e3939786f347 100644 --- a/drivers/extcon/Makefile +++ b/drivers/extcon/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_EXTCON_PALMAS) +=3D extcon-palmas.o obj-$(CONFIG_EXTCON_PTN5150) +=3D extcon-ptn5150.o obj-$(CONFIG_EXTCON_QCOM_SPMI_MISC) +=3D extcon-qcom-spmi-misc.o obj-$(CONFIG_EXTCON_RT8973A) +=3D extcon-rt8973a.o +obj-$(CONFIG_EXTCON_S2M) +=3D extcon-s2m.o obj-$(CONFIG_EXTCON_SM5502) +=3D extcon-sm5502.o obj-$(CONFIG_EXTCON_USB_GPIO) +=3D extcon-usb-gpio.o obj-$(CONFIG_EXTCON_USBC_CROS_EC) +=3D extcon-usbc-cros-ec.o diff --git a/drivers/extcon/extcon-s2m.c b/drivers/extcon/extcon-s2m.c new file mode 100644 index 000000000000..b27ee9e79bb6 --- /dev/null +++ b/drivers/extcon/extcon-s2m.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Extcon Driver for Samsung S2M series PMICs. + * + * Copyright (c) 2015 Samsung Electronics Co., Ltd + * Copyright (C) 2025 Kaustabh Chakraborty + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct s2m_muic { + struct device *dev; + struct regmap *regmap; + struct extcon_dev *extcon; + struct s2m_muic_irq_data *irq_data; + const unsigned int *extcon_cable; + bool attached; +}; + +struct s2m_muic_irq_data { + const enum s2mu005_muic_irq irq; + const char *name; + int (* const handler)(struct s2m_muic *); + int virq; +}; + +static int s2mu005_muic_detach(struct s2m_muic *priv) +{ + int ret; + int i; + + ret =3D regmap_set_bits(priv->regmap, S2MU005_REG_MUIC_CTRL1, + S2MU005_MUIC_MAN_SW); + if (ret < 0) { + dev_err(priv->dev, "failed to disable manual switching\n"); + return ret; + } + + ret =3D regmap_set_bits(priv->regmap, S2MU005_REG_MUIC_CTRL3, + S2MU005_MUIC_ONESHOT_ADC); + if (ret < 0) { + dev_err(priv->dev, "failed to enable ADC oneshot mode\n"); + return ret; + } + + ret =3D regmap_clear_bits(priv->regmap, S2MU005_REG_MUIC_SWCTRL, ~0); + if (ret < 0) { + dev_err(priv->dev, "failed to clear switch control register\n"); + return ret; + } + + /* Find all set states and clear them */ + for (i =3D 0; priv->extcon_cable[i]; i++) { + unsigned int state =3D priv->extcon_cable[i]; + + if (extcon_get_state(priv->extcon, state) =3D=3D true) + extcon_set_state_sync(priv->extcon, state, false); + } + + priv->attached =3D false; + + return 0; +} + +static int s2mu005_muic_attach(struct s2m_muic *priv) +{ + unsigned int type; + int ret; + + /* If any device is already attached, detach it */ + if (priv->attached) { + s2mu005_muic_detach(priv); + msleep(100); + } + + ret =3D regmap_read(priv->regmap, S2MU005_REG_MUIC_DEV1, &type); + if (ret < 0) { + dev_err(priv->dev, "failed to read DEV1 register\n"); + return ret; + } + + /* + * All USB connections which require communication via its D+ + * and D- wires need it. + */ + if (type & (S2MU005_MUIC_OTG | S2MU005_MUIC_DCP | S2MU005_MUIC_SDP)) { + ret =3D regmap_update_bits(priv->regmap, S2MU005_REG_MUIC_SWCTRL, + S2MU005_MUIC_DM_DP, + S2MU005_MUIC_DM_DP_USB); + if (ret < 0) { + dev_err(priv->dev, "failed to configure DM/DP pins\n"); + return ret; + } + } + + /* + * For OTG connections, enable manual switching and ADC oneshot + * mode. Since the port will now be supplying power, the + * internal ADC (measuring the ID-GND resistance) is made to + * poll periodically for any changes, so as to prevent any + * damages due to power. + */ + if (type & S2MU005_MUIC_OTG) { + ret =3D regmap_clear_bits(priv->regmap, S2MU005_REG_MUIC_CTRL1, + S2MU005_MUIC_MAN_SW); + if (ret < 0) { + dev_err(priv->dev, "failed to enable manual switching\n"); + return ret; + } + + ret =3D regmap_clear_bits(priv->regmap, S2MU005_REG_MUIC_CTRL3, + S2MU005_MUIC_ONESHOT_ADC); + if (ret < 0) { + dev_err(priv->dev, "failed to disable ADC oneshot mode\n"); + return ret; + } + } + + switch (type) { + case S2MU005_MUIC_OTG: + dev_dbg(priv->dev, "USB OTG connection detected\n"); + extcon_set_state_sync(priv->extcon, EXTCON_USB_HOST, true); + priv->attached =3D true; + break; + case S2MU005_MUIC_CDP: + dev_dbg(priv->dev, "USB CDP connection detected\n"); + extcon_set_state_sync(priv->extcon, EXTCON_USB, true); + extcon_set_state_sync(priv->extcon, EXTCON_CHG_USB_CDP, true); + priv->attached =3D true; + break; + case S2MU005_MUIC_SDP: + dev_dbg(priv->dev, "USB SDP connection detected\n"); + extcon_set_state_sync(priv->extcon, EXTCON_USB, true); + extcon_set_state_sync(priv->extcon, EXTCON_CHG_USB_SDP, true); + priv->attached =3D true; + break; + case S2MU005_MUIC_DCP: + dev_dbg(priv->dev, "USB DCP connection detected\n"); + extcon_set_state_sync(priv->extcon, EXTCON_USB, true); + extcon_set_state_sync(priv->extcon, EXTCON_CHG_USB_DCP, true); + priv->attached =3D true; + break; + case S2MU005_MUIC_UART: + dev_dbg(priv->dev, "UART connection detected\n"); + extcon_set_state_sync(priv->extcon, EXTCON_JIG, true); + priv->attached =3D true; + break; + } + + if (!priv->attached) + dev_warn(priv->dev, "failed to recognize the device attached\n"); + + return ret; +} + +static int s2mu005_muic_init(struct s2m_muic *priv) +{ + int ret =3D 0; + + ret =3D regmap_update_bits(priv->regmap, S2MU005_REG_MUIC_LDOADC_L, + S2MU005_MUIC_VSET, S2MU005_MUIC_VSET_3P0V); + if (ret < 0) { + dev_err(priv->dev, "failed to set internal ADC voltage regulator\n"); + return ret; + } + + ret =3D regmap_update_bits(priv->regmap, S2MU005_REG_MUIC_LDOADC_H, + S2MU005_MUIC_VSET, S2MU005_MUIC_VSET_3P0V); + if (ret < 0) { + dev_err(priv->dev, "failed to set internal ADC voltage regulator\n"); + return ret; + } + + ret =3D regmap_clear_bits(priv->regmap, S2MU005_REG_MUIC_CTRL1, + S2MU005_MUIC_IRQ); + if (ret < 0) { + dev_err(priv->dev, "failed to enable MUIC interrupts\n"); + return ret; + } + + return s2mu005_muic_attach(priv); +} + +static const unsigned int s2mu005_muic_extcon_cable[] =3D { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_CHG_USB_SDP, + EXTCON_CHG_USB_DCP, + EXTCON_CHG_USB_CDP, + EXTCON_JIG, + EXTCON_NONE, +}; + +static struct s2m_muic_irq_data s2mu005_muic_irq_data[] =3D { + { + .irq =3D S2MU005_MUIC_IRQ_ATTACH, + .name =3D "s2mu005-muic-attach", + .handler =3D s2mu005_muic_attach + }, { + .irq =3D S2MU005_MUIC_IRQ_DETACH, + .name =3D "s2mu005-muic-detach", + .handler =3D s2mu005_muic_detach + }, { + /* sentinel */ + } +}; + +static irqreturn_t s2m_muic_irq_func(int virq, void *data) +{ + struct s2m_muic *priv =3D data; + const struct s2m_muic_irq_data *irq_data =3D priv->irq_data; + int ret; + int i; + + for (i =3D 0; irq_data[i].handler; i++) { + if (virq !=3D irq_data[i].virq) + continue; + + ret =3D irq_data[i].handler(priv); + if (ret < 0) + dev_err(priv->dev, "failed to handle interrupt for %s (%d)\n", + irq_data[i].name, ret); + break; + } + + return IRQ_HANDLED; +} + +static int s2m_muic_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct sec_pmic_dev *pmic_drvdata =3D dev_get_drvdata(dev->parent); + struct s2m_muic *priv; + struct regmap_irq_chip_data *irq_chip_data; + int ret; + int i; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return dev_err_probe(dev, -ENOMEM, "failed to allocate driver private\n"= ); + + platform_set_drvdata(pdev, priv); + priv->dev =3D dev; + priv->regmap =3D pmic_drvdata->regmap_pmic; + + switch (platform_get_device_id(pdev)->driver_data) { + case S2MU005: + irq_chip_data =3D pmic_drvdata->irq_data[S2MU005_MUIC_IRQ_CHIP]; + priv->irq_data =3D s2mu005_muic_irq_data; + priv->extcon_cable =3D s2mu005_muic_extcon_cable; + /* Initialize MUIC */ + ret =3D s2mu005_muic_init(priv); + break; + default: + return dev_err_probe(dev, -ENODEV, + "device type %d is not supported by driver\n", + pmic_drvdata->device_type); + } + if (ret < 0) + return dev_err_probe(dev, ret, "failed to initialize MUIC\n"); + + priv->extcon =3D devm_extcon_dev_allocate(&pdev->dev, priv->extcon_cable); + if (IS_ERR(priv->extcon)) + return dev_err_probe(dev, PTR_ERR(priv->extcon), + "failed to allocate memory for extcon\n"); + + ret =3D devm_extcon_dev_register(dev, priv->extcon); + if (ret) + return dev_err_probe(dev, ret, "failed to register extcon device\n"); + + for (i =3D 0; priv->irq_data[i].handler; i++) { + int virq =3D regmap_irq_get_virq(irq_chip_data, + priv->irq_data[i].irq); + if (virq <=3D 0) + return dev_err_probe(dev, -EINVAL, "failed to get virtual IRQ\n"); + + priv->irq_data[i].virq =3D virq; + ret =3D devm_request_threaded_irq(dev, virq, NULL, + s2m_muic_irq_func, IRQF_ONESHOT, + priv->irq_data[i].name, priv); + if (ret) + dev_err_probe(dev, ret, "failed to request IRQ\n"); + } + + return 0; +} + +static void s2m_muic_remove(struct platform_device *pdev) +{ + struct s2m_muic *priv =3D dev_get_drvdata(&pdev->dev); + + /* + * Disabling the MUIC device is important as it disables manual + * switching mode, thereby enabling auto switching mode. + * + * This is to ensure that when the board is powered off, it + * goes into LPM charging mode when a USB charger is connected. + */ + switch (platform_get_device_id(pdev)->driver_data) { + case S2MU005: + s2mu005_muic_detach(priv); + break; + } +} + +static const struct platform_device_id s2m_muic_id_table[] =3D { + { "s2mu005-muic", S2MU005 }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, s2m_muic_id_table); + +#ifdef CONFIG_OF +/* + * Device is instantiated through parent MFD device and device matching + * is done through platform_device_id. + * + * However if device's DT node contains proper compatible and driver is + * built as a module, then the *module* matching will be done through DT + * aliases. This requires of_device_id table. In the same time this will + * not change the actual *device* matching so do not add .of_match_table. + */ +static const struct of_device_id s2m_muic_of_match_table[] =3D { + { + .compatible =3D "samsung,s2mu005-muic", + .data =3D (void *)S2MU005, + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, s2m_muic_of_match_table); +#endif + +static struct platform_driver s2m_muic_driver =3D { + .driver =3D { + .name =3D "s2m-muic", + }, + .probe =3D s2m_muic_probe, + .remove =3D s2m_muic_remove, + .id_table =3D s2m_muic_id_table, +}; +module_platform_driver(s2m_muic_driver); + +MODULE_DESCRIPTION("Extcon Driver For Samsung S2M Series PMICs"); +MODULE_AUTHOR("Kaustabh Chakraborty "); +MODULE_LICENSE("GPL"); --=20 2.51.2