From patchwork Tue Jun 18 13:59:46 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dzmitry Sankouski X-Patchwork-Id: 806850 Received: from mail-ed1-f44.google.com (mail-ed1-f44.google.com [209.85.208.44]) (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 D0264157478; Tue, 18 Jun 2024 14:00:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1718719219; cv=none; b=o6NPLx2Et5WGty6MzitPHpAOIU8jhj6V2pCUuNzL7SCuWNHyuwm/fshRqg8skc1OYxJCJC4qnJRoJQzMo0t+24N/pCXXv5zrd09oNVF4SiD1ijc8CT/Izna9SfxoLG29FbZIWJCK1fx6Y8TWjLl2STf+mPukUGQStor12JYv4jQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1718719219; c=relaxed/simple; bh=B1lA/Gc2Dl8Wk4SsWnJx6fkj18H3BhQFhk0y5dNlehE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=apnDy0KdilZVfwENtgkV1BJi5Oy48rr8RjiWsHdVBr6vJXAlIzLEA3vMP3d7B7j1yDE3v5BzOuW3hTPnWiNXD3y4g4AdVII5YUWZUNhEnLjEZSpztb7emyl2Hahx7OJ71Z4JHOyozzB/EFV1rSxXJPFvOQ9LJieZMFace2QJDz0= 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=hG8Od84C; arc=none smtp.client-ip=209.85.208.44 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="hG8Od84C" Received: by mail-ed1-f44.google.com with SMTP id 4fb4d7f45d1cf-57cad452f8bso6207800a12.2; Tue, 18 Jun 2024 07:00:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1718719215; x=1719324015; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=lLO6JQTUDUiwTtdmLYnaDKzuAgvre8uTKnhhOLvppR0=; b=hG8Od84CmvLO9zyfdqrL7of+pXMR0oU5tYmfvidz9rCtwc7KwbzxJxIlNnTlBtDRk2 lwcr6OIvBKNsvu4J+HBVLq7U7MeqKxhrJFtv7+47PrDFbVd23oKtcmLuWXFQ8hC2L9uX 4Ep/zZARnjB/TMd97idcCWWkQWW6TRiiyisZzWLkGl96VAms6emM7TPzh0w3y8jSMiIl G06ujihhNkiFD9aqmrS1Ovlt2+0Qj2iuAkRiYCP4fm+Y9QAilhjQxEj3FfNGRlquloXz vjvgTUuoFujh7DiQbOnBCuwlBog3NeCYKa1s2zxzAQTHU8NAEzlaWCy85y2KBHPB2Xap yScQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1718719215; x=1719324015; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=lLO6JQTUDUiwTtdmLYnaDKzuAgvre8uTKnhhOLvppR0=; b=LedF4+AA363ajSZuffQjvE1SXAkOgedYLiR9KHf0uX/0VmK6YOd97EWMAT8FQDTJou baHu54xvrpS860lJMZQWjjfqQnVgMsQnInEA/wiMpfeaxJd+Ny4IZEncyXIV5ynwQT8x fPcW8DX1/q8r6SkJhEe567fnbgoJLciZkLgmRx4qT70LIz8oIzW2Yw3qzVjsOmPqEY5x eAozdrKEji0HdSb/fUKqh93HN+a1vicn6s9rbgJfeXCTq9PxBiBTXJ1V8+BRx42Ewx8O 6T1TUyeKysjdmiJiBwD6wgpcOGlqz2jCxWcVw4W/LzINXn7fApqdFvL38FHV/KwwNY+C sNbg== X-Forwarded-Encrypted: i=1; AJvYcCWKfiuuybN8z4SGH2Uukiwt3gB2Su0myjSuR0slBJG+31zDFVsB/UkdRmwtmoSh9ImR1vGItcXYtCFpV1T3f4ixKFyyj+WvOnhBnmDcaQUtqQfWGy2AhkAlL3byfTYYiUz82LkAfAeqzHGqLzQuGSikdEzQiHmCtX98N7KbhNMioRWX/yhd2S0axFdHtVBk3x15UOIIkDZ/U4vkA1U3L1KR9F9PGpJ1FSwsjrGEL3LCZl8sLxdtVPMa4CXPukKJ8py/PbrXBG7CUDOzeKchAH1h2wGVzcYRC9TqaJ3wRLD/OXPoP1fMw7RZwnynSBpD5R0LtIcs8Wyg9wpeh7JkB0X0CjMYzqUDJyBmsw+aqSEK7mNAheYTacbplNDNwxJsxxE1UHaDpd9BEPGa5Zh1AknVUxZMXSHH X-Gm-Message-State: AOJu0Yyz0kHrFUqAOhuI9cyqoUoEbCQANcZzJmWCD/bJ/01ZmuPg/fuw Jh6Vdit4OgS2jhfoSVfmvSMr4zPfBZ+46AjuhNXfHfCqLGEN93mU X-Google-Smtp-Source: AGHT+IG8F54EN9ZsZzLwwzz6jnEUOnhvhk6d6nAeqIs3gJ5a91heFass+KAkwkGBVlZZE2e6l8v3lQ== X-Received: by 2002:a50:9b45:0:b0:57c:74ed:88c5 with SMTP id 4fb4d7f45d1cf-57cbd665244mr8105891a12.13.1718719214864; Tue, 18 Jun 2024 07:00:14 -0700 (PDT) Received: from [127.0.1.1] (mm-167-232-122-178.mgts.dynamic.pppoe.byfly.by. [178.122.232.167]) by smtp.googlemail.com with ESMTPSA id 4fb4d7f45d1cf-57cb72da156sm7731278a12.22.2024.06.18.07.00.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 18 Jun 2024 07:00:14 -0700 (PDT) From: Dzmitry Sankouski Date: Tue, 18 Jun 2024 16:59:46 +0300 Subject: [PATCH v3 12/23] mfd: Add new driver for MAX77705 PMIC Precedence: bulk X-Mailing-List: linux-leds@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-Id: <20240618-starqltechn_integration_upstream-v3-12-e3f6662017ac@gmail.com> References: <20240618-starqltechn_integration_upstream-v3-0-e3f6662017ac@gmail.com> In-Reply-To: <20240618-starqltechn_integration_upstream-v3-0-e3f6662017ac@gmail.com> To: Sebastian Reichel , Bjorn Andersson , Michael Turquette , Stephen Boyd , Neil Armstrong , Jessica Zhang , Sam Ravnborg , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Daniel Vetter , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Lee Jones , Dmitry Torokhov , Pavel Machek , Liam Girdwood , Mark Brown , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Krzysztof Kozlowski , Konrad Dybcio , Chanwoo Choi , phone-devel@vger.kernel.org Cc: linux-pm@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-msm@vger.kernel.org, linux-clk@vger.kernel.org, dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org, linux-input@vger.kernel.org, linux-leds@vger.kernel.org, linux-pwm@vger.kernel.org, linux-samsung-soc@vger.kernel.org, Dzmitry Sankouski , Rose Hudson X-Mailer: b4 0.14.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1718719184; l=29057; i=dsankouski@gmail.com; s=20240618; h=from:subject:message-id; bh=B1lA/Gc2Dl8Wk4SsWnJx6fkj18H3BhQFhk0y5dNlehE=; b=VrQOGho78Detw4YGoUazfrViU0atCSCbuOT49p7DjXujGtg8bWdeliNr4KIw23EIwEIax4u+W TY9bdwEhAeSC8TmtcVdjzgDNk/yC0sJVlKpcJAgQIHVkwu3Zr1gZxac X-Developer-Key: i=dsankouski@gmail.com; a=ed25519; pk=6pMMVVDDReSiRgPCbMOUauN5nS3ty4Sf5b7a2gi4x0M= Add the core MFD driver for max77705 PMIC. We define five sub-devices for which the drivers will be added in subsequent patches. Signed-off-by: Dzmitry Sankouski Co-authored-by: Rose Hudson --- MAINTAINERS | 2 + drivers/mfd/Kconfig | 12 ++ drivers/mfd/Makefile | 3 + drivers/mfd/max77705-core.c | 278 ++++++++++++++++++++++++++++++++ drivers/mfd/max77705-irq.c | 299 +++++++++++++++++++++++++++++++++++ include/linux/mfd/max77705-private.h | 281 ++++++++++++++++++++++++++++++++ include/linux/mfd/max77705.h | 20 +++ 7 files changed, 895 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index fae3b8ea9ce4..f66f08825db9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13690,6 +13690,7 @@ F: drivers/*/*max77843.c F: drivers/*/max14577*.c F: drivers/*/max77686*.c F: drivers/*/max77693*.c +F: drivers/*/max77705*.c F: drivers/clk/clk-max77686.c F: drivers/extcon/extcon-max14577.c F: drivers/extcon/extcon-max77693.c @@ -13697,6 +13698,7 @@ F: drivers/rtc/rtc-max77686.c F: include/linux/mfd/max14577*.h F: include/linux/mfd/max77686*.h F: include/linux/mfd/max77693*.h +F: include/linux/mfd/max77705*.h MAXIRADIO FM RADIO RECEIVER DRIVER M: Hans Verkuil diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 266b4f54af60..c4eb8ff2dcad 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -880,6 +880,18 @@ config MFD_MAX77693 additional drivers must be enabled in order to use the functionality of the device. +config MFD_MAX77705 + tristate "Maxim Semiconductor MAX77705 PMIC Support" + depends on I2C + select MFD_CORE + help + Say yes here to add support for Maxim Semiconductor MAX77705. + This is a Power Management IC with Charger, safe LDOs, Flash, Haptic + and MUIC controls on chip. + This driver provides common support for accessing the device; + additional drivers must be enabled in order to use the functionality + of the device. + config MFD_MAX77714 tristate "Maxim Semiconductor MAX77714 PMIC Support" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index db1ba39de3b5..bf65cc36d59c 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -162,6 +162,8 @@ obj-$(CONFIG_MFD_MAX77620) += max77620.o obj-$(CONFIG_MFD_MAX77650) += max77650.o obj-$(CONFIG_MFD_MAX77686) += max77686.o obj-$(CONFIG_MFD_MAX77693) += max77693.o +max77705-objs := max77705-core.o max77705-irq.o +obj-$(CONFIG_MFD_MAX77705) += max77705.o obj-$(CONFIG_MFD_MAX77714) += max77714.o obj-$(CONFIG_MFD_MAX77843) += max77843.o obj-$(CONFIG_MFD_MAX8907) += max8907.o @@ -226,6 +228,7 @@ obj-$(CONFIG_MFD_RK8XX_I2C) += rk8xx-i2c.o obj-$(CONFIG_MFD_RK8XX_SPI) += rk8xx-spi.o obj-$(CONFIG_MFD_RN5T618) += rn5t618.o obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o +obj-$(CONFIG_MFD_S2DOS05) += s2dos05.o obj-$(CONFIG_MFD_SYSCON) += syscon.o obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o obj-$(CONFIG_MFD_VEXPRESS_SYSREG) += vexpress-sysreg.o diff --git a/drivers/mfd/max77705-core.c b/drivers/mfd/max77705-core.c new file mode 100644 index 000000000000..7cb71a0a9688 --- /dev/null +++ b/drivers/mfd/max77705-core.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * max77705.c - mfd core driver for the Maxim 77705 + * + * 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. + * + * Copyright (C) 2024 Dzmitry Sankouski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define I2C_ADDR_PMIC (0xCC >> 1) /* Top sys, Haptic */ +#define I2C_ADDR_MUIC (0x4A >> 1) +#define I2C_ADDR_CHG (0xD2 >> 1) +#define I2C_ADDR_FG (0x6C >> 1) +#define I2C_ADDR_DEBUG (0xC4 >> 1) + +static struct dentry *debugfs_file; + +static int max77705_debugfs_show(struct seq_file *s, void *data) +{ + struct max77705_dev *max77705 = s->private; + struct regmap *regmap = max77705->regmap; + unsigned int i, reg, reg_data, pmic_id, pmic_rev; + int regs[] = { + MAX77705_PMIC_REG_MAINCTRL1, + MAX77705_PMIC_REG_MCONFIG, + MAX77705_PMIC_REG_MCONFIG2, + MAX77705_PMIC_REG_INTSRC, + MAX77705_PMIC_REG_INTSRC_MASK, + MAX77705_PMIC_REG_SYSTEM_INT, + MAX77705_PMIC_REG_SYSTEM_INT_MASK, + MAX77705_RGBLED_REG_LEDEN, + MAX77705_RGBLED_REG_LED0BRT, + MAX77705_RGBLED_REG_LED1BRT, + MAX77705_RGBLED_REG_LED2BRT, + MAX77705_RGBLED_REG_LED3BRT, + MAX77705_RGBLED_REG_LEDBLNK + }; + + regmap_read(regmap, MAX77705_PMIC_REG_PMICID1, &pmic_id); + regmap_read(regmap, MAX77705_PMIC_REG_PMICREV, &pmic_rev); + seq_printf(s, "MAX77705, pmic id: %d, pmic rev: %d\n", + pmic_id, pmic_rev); + seq_puts(s, "===================\n"); + for (i = 0; i < ARRAY_SIZE(regs); i++) { + reg = regs[i]; + regmap_read(regmap, reg, ®_data); + seq_printf(s, "0x%02x:\t0x%02x\n", reg, reg_data); + } + + seq_puts(s, "\n"); + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(max77705_debugfs); + +static const struct regmap_config max77705_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77705_PMIC_REG_END, +}; + +static const struct regmap_config max77705_leds_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77705_LED_REG_END, +}; + +static const struct regmap_config max77705_fg_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77705_FG_END, +}; + +static struct mfd_cell max77705_devs[] = { + { + .name = "leds-max77705-rgb", + .of_compatible = "maxim,max77705-led", + }, + { + .name = "max77705-fuelgauge", + .of_compatible = "maxim,max77705-fg", + }, + { + .name = "max77705-charger", + .of_compatible = "maxim,max77705-charger", + }, + { + .name = "max77705-haptic", + .of_compatible = "maxim,max77705-haptic", + }, +}; + +static int max77705_i2c_probe(struct i2c_client *i2c) +{ + struct max77705_dev *max77705; + struct max77705_platform_data *pdata = i2c->dev.platform_data; + + unsigned int reg_data; + int ret = 0; + + max77705 = kzalloc(sizeof(struct max77705_dev), GFP_KERNEL); + if (!max77705) + return -ENOMEM; + + max77705->pdata = pdata; + max77705->dev = &i2c->dev; + max77705->i2c = i2c; + max77705->irq = i2c->irq; + + max77705->regmap = devm_regmap_init_i2c(max77705->i2c, &max77705_regmap_config); + if (IS_ERR(max77705->regmap)) { + ret = PTR_ERR(max77705->regmap); + dev_err(max77705->dev, "failed to allocate register map: %d\n", + ret); + return ret; + } + + max77705->regmap_leds = devm_regmap_init_i2c(max77705->i2c, &max77705_leds_regmap_config); + if (IS_ERR(max77705->regmap_leds)) { + ret = PTR_ERR(max77705->regmap_leds); + dev_err(max77705->dev, "failed to allocate register map: %d\n", + ret); + return ret; + } + + i2c_set_clientdata(i2c, max77705); + + if (regmap_read(max77705->regmap, MAX77705_PMIC_REG_PMICREV, ®_data) < 0) { + dev_err(max77705->dev, + "device not found on this channel (this is not an error)\n"); + ret = -ENODEV; + goto err; + } else { + /* print rev */ + max77705->pmic_rev = (reg_data & MAX77705_REVISION_MASK); + max77705->pmic_ver = ((reg_data & MAX77705_VERSION_MASK) >> MAX77705_VERSION_SHIFT); + dev_info(max77705->dev, "%s device found: rev.0x%x, ver.0x%x\n", + __func__, max77705->pmic_rev, max77705->pmic_ver); + } + + max77705->charger = devm_i2c_new_dummy_device(max77705->dev, i2c->adapter, I2C_ADDR_CHG); + i2c_set_clientdata(max77705->charger, max77705); + max77705->regmap_charger = devm_regmap_init_i2c(max77705->charger, &max77705_regmap_config); + if (IS_ERR(max77705->regmap)) { + ret = PTR_ERR(max77705->regmap); + dev_err(max77705->dev, "failed to allocate register map: %d\n", + ret); + return ret; + } + + max77705->fuelgauge = devm_i2c_new_dummy_device(max77705->dev, i2c->adapter, I2C_ADDR_FG); + i2c_set_clientdata(max77705->fuelgauge, max77705); + max77705->regmap_fg = devm_regmap_init_i2c(max77705->fuelgauge, &max77705_fg_regmap_config); + if (IS_ERR(max77705->regmap_fg)) { + ret = PTR_ERR(max77705->regmap_fg); + dev_err(max77705->dev, "failed to allocate register map: %d\n", + ret); + return ret; + } + + if (likely(i2c->irq > 0)) + max77705->irq = i2c->irq; + else { + dev_err(max77705->dev, "failed to get irq number\n"); + return -EINVAL; + } + + max77705->irq_base = irq_alloc_descs(-1, 0, MAX77705_IRQ_NR, -1); + if (unlikely(max77705->irq_base < 0)) { + dev_err(max77705->dev, "irq_alloc_descs fail: %d\n", max77705->irq_base); + ret = -EINVAL; + goto err; + } + + disable_irq(max77705->irq); + ret = max77705_irq_init(max77705); + if (ret) { + dev_err(max77705->dev, "failed to init irq system: %d\n", ret); + return ret; + } + + ret = mfd_add_devices(max77705->dev, -1, max77705_devs, + ARRAY_SIZE(max77705_devs), NULL, 0, NULL); + if (ret < 0) + goto err_mfd; + + debugfs_file = debugfs_create_file("max77705-regs", + 0664, NULL, (void *)max77705, + &max77705_debugfs_fops); + if (!debugfs_file) + dev_err(max77705->dev, "Failed to create debugfs file\n"); + + device_init_wakeup(max77705->dev, true); + + return ret; + +err_mfd: + mfd_remove_devices(max77705->dev); +err: + kfree(max77705); + return ret; +} + +static void max77705_i2c_remove(struct i2c_client *i2c) +{ + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); + + if (debugfs_file) + debugfs_remove(debugfs_file); + + device_init_wakeup(max77705->dev, 0); + mfd_remove_devices(max77705->dev); +} + +static int __maybe_unused max77705_suspend(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); + + disable_irq(max77705->irq); + if (device_may_wakeup(dev)) + enable_irq_wake(max77705->irq); + + return 0; +} + +static int __maybe_unused max77705_resume(struct device *dev) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct max77705_dev *max77705 = i2c_get_clientdata(i2c); + + if (device_may_wakeup(dev)) + disable_irq_wake(max77705->irq); + enable_irq(max77705->irq); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(max77705_pm, max77705_suspend, max77705_resume); + +static const struct of_device_id max77705_i2c_dt_ids[] = { + { .compatible = "maxim,max77705" }, + { }, +}; +MODULE_DEVICE_TABLE(of, max77705_i2c_dt_ids); + +static struct i2c_driver max77705_i2c_driver = { + .driver = { + .name = MFD_DEV_NAME, + .pm = &max77705_pm, + .of_match_table = max77705_i2c_dt_ids, + .suppress_bind_attrs = true, + }, + .probe = max77705_i2c_probe, + .remove = max77705_i2c_remove, +}; +module_i2c_driver(max77705_i2c_driver); + +MODULE_DESCRIPTION("MAXIM 77705 multi-function core driver"); +MODULE_AUTHOR("Dzmitry Sankouski "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/max77705-irq.c b/drivers/mfd/max77705-irq.c new file mode 100644 index 000000000000..0da17fc95e18 --- /dev/null +++ b/drivers/mfd/max77705-irq.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * max77705-irq.c - Interrupt controller support for MAX77705 + * + * 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. + * + * Copyright (C) 2024 Dzmitry Sankouski + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const u8 max77705_mask_reg[] = { + [SYS_INT] = MAX77705_PMIC_REG_SYSTEM_INT_MASK, + [CHG_INT] = MAX77705_CHG_REG_INT_MASK, + [FUEL_INT] = MAX77705_REG_INVALID, +}; + +static struct regmap *get_i2c(struct max77705_dev *max77705, + enum max77705_irq_source src) +{ + switch (src) { + case SYS_INT: + return max77705->regmap; + case FUEL_INT: + return max77705->regmap_fg; + case CHG_INT: + return max77705->regmap_charger; + default: + return ERR_PTR(-EINVAL); + } +} + +struct max77705_irq_data { + int mask; + enum max77705_irq_source group; +}; + +static const struct max77705_irq_data max77705_irqs[] = { + [MAX77705_SYSTEM_IRQ_BSTEN_INT] = { .group = SYS_INT, .mask = BIT(3) }, + [MAX77705_SYSTEM_IRQ_SYSUVLO_INT] = { .group = SYS_INT, .mask = BIT(4) }, + [MAX77705_SYSTEM_IRQ_SYSOVLO_INT] = { .group = SYS_INT, .mask = BIT(5) }, + [MAX77705_SYSTEM_IRQ_TSHDN_INT] = { .group = SYS_INT, .mask = BIT(6) }, + [MAX77705_SYSTEM_IRQ_TM_INT] = { .group = SYS_INT, .mask = BIT(7) }, + + [MAX77705_CHG_IRQ_BYP_I] = { .group = CHG_INT, .mask = BIT(0) }, + [MAX77705_CHG_IRQ_BAT_I] = { .group = CHG_INT, .mask = BIT(3) }, + [MAX77705_CHG_IRQ_CHG_I] = { .group = CHG_INT, .mask = BIT(4) }, + [MAX77705_CHG_IRQ_WCIN_I] = { .group = CHG_INT, .mask = BIT(5) }, + [MAX77705_CHG_IRQ_CHGIN_I] = { .group = CHG_INT, .mask = BIT(6) }, + [MAX77705_CHG_IRQ_AICL_I] = { .group = CHG_INT, .mask = BIT(7) }, + + [MAX77705_FG_IRQ_ALERT] = { .group = FUEL_INT, .mask = BIT(1) }, +}; + +static void max77705_irq_lock(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + + mutex_lock(&max77705->irqlock); +} + +static void max77705_irq_sync_unlock(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + int i; + + for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) { + u8 mask_reg = max77705_mask_reg[i]; + struct regmap *i2c = get_i2c(max77705, i); + + if (mask_reg == MAX77705_REG_INVALID || + IS_ERR_OR_NULL(i2c)) + continue; + max77705->irq_masks_cache[i] = max77705->irq_masks_cur[i]; + + regmap_write(i2c, max77705_mask_reg[i], + max77705->irq_masks_cur[i]); + } + + mutex_unlock(&max77705->irqlock); +} + +static inline void max77705_read_irq_reg(struct regmap *regmap, unsigned int pmic_rev, + unsigned int reg, unsigned int *irq_src) { + u8 dummy[2] = {0, }; /* for pass1 intr reg clear issue */ + + switch (pmic_rev) { + case MAX77705_PASS1: + regmap_noinc_read(regmap, reg - 1, + dummy, sizeof(dummy)); + *irq_src = (unsigned int) dummy[1]; + break; + case MAX77705_PASS2: + case MAX77705_PASS3: + regmap_read(regmap, reg, + irq_src); + break; + default: + pr_err("%s: PMIC_REVISION(SRC_CHG) isn't valid\n", __func__); + break; + } +} + +static inline const struct max77705_irq_data * +irq_to_max77705_irq(struct max77705_dev *max77705, int irq) +{ + return &max77705_irqs[irq - max77705->irq_base]; +} + +static void max77705_irq_mask(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + const struct max77705_irq_data *irq_data = + irq_to_max77705_irq(max77705, data->irq); + + if (irq_data->group >= MAX77705_IRQ_GROUP_NR) + return; + + max77705->irq_masks_cur[irq_data->group] |= irq_data->mask; +} + +static void max77705_irq_unmask(struct irq_data *data) +{ + struct max77705_dev *max77705 = irq_get_chip_data(data->irq); + const struct max77705_irq_data *irq_data = + irq_to_max77705_irq(max77705, data->irq); + + if (irq_data->group >= MAX77705_IRQ_GROUP_NR) + return; + + max77705->irq_masks_cur[irq_data->group] &= ~irq_data->mask; +} + +inline int max77705_irq_mask_subdevice(struct max77705_dev *max77705, unsigned int mask) +{ + int ret; + unsigned int data; + + ret = regmap_read(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK, + &data); + if (ret) { + dev_err(max77705->dev, "fail to read MAX77705_PMIC_REG_INTSRC_MASK reg\n"); + return ret; + } + data |= mask; + + regmap_write(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK, + data); + return 0; +} +EXPORT_SYMBOL_GPL(max77705_irq_mask_subdevice); + +inline int max77705_irq_unmask_subdevice(struct max77705_dev *max77705, unsigned int mask) +{ + int ret; + unsigned int data; + + ret = regmap_read(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK, + &data); + if (ret) { + dev_err(max77705->dev, "fail to read MAX77705_PMIC_REG_INTSRC_MASK reg\n"); + return ret; + } + data &= ~(mask); + + regmap_write(max77705->regmap, MAX77705_PMIC_REG_INTSRC_MASK, + data); + return 0; +} +EXPORT_SYMBOL_GPL(max77705_irq_unmask_subdevice); + +static void max77705_irq_disable(struct irq_data *data) +{ + max77705_irq_mask(data); +} + +static struct irq_chip max77705_irq_chip = { + .name = MFD_DEV_NAME, + .irq_bus_lock = max77705_irq_lock, + .irq_bus_sync_unlock = max77705_irq_sync_unlock, + .irq_mask = max77705_irq_mask, + .irq_unmask = max77705_irq_unmask, + .irq_disable = max77705_irq_disable, +}; + +static irqreturn_t max77705_irq_thread(int irq, void *data) +{ + struct max77705_dev *max77705 = data; + unsigned int irq_reg[MAX77705_IRQ_GROUP_NR] = {0}; + unsigned int irq_src; + int i, ret; + u8 pmic_rev = max77705->pmic_rev; + + max77705->doing_irq = 1; + + ret = regmap_read(max77705->regmap, + MAX77705_PMIC_REG_INTSRC, &irq_src); + if (ret) { + pr_err("%s:%s Failed to read interrupt source: %d\n", + MFD_DEV_NAME, __func__, ret); + + max77705->doing_irq = 0; + return IRQ_NONE; + } + + if (irq_src & MAX77705_IRQSRC_CHG) { + max77705_read_irq_reg(max77705->regmap_charger, pmic_rev, + MAX77705_CHG_REG_INT, &irq_reg[CHG_INT]); + pr_info("%s: charger interrupt(0x%02x)\n", + __func__, irq_reg[CHG_INT]); + } + + /* Apply masking */ + for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) + irq_reg[i] &= ~max77705->irq_masks_cur[i]; + + /* Report */ + for (i = 0; i < MAX77705_IRQ_NR; i++) { + if (irq_reg[max77705_irqs[i].group] & max77705_irqs[i].mask) + handle_nested_irq(max77705->irq_base + i); + } + + max77705->doing_irq = 0; + + return IRQ_HANDLED; +} + +int max77705_irq_init(struct max77705_dev *max77705) +{ + int i; + int ret = 0; + int cur_irq; + + if (!max77705->irq_base) { + dev_err(max77705->dev, "No interrupt base specified.\n"); + return 0; + } + + mutex_init(&max77705->irqlock); + + /* Mask individual interrupt sources */ + for (i = 0; i < MAX77705_IRQ_GROUP_NR; i++) { + struct regmap *i2c; + /* MUIC IRQ 0:MASK 1:NOT MASK => NOT USE */ + /* Other IRQ 1:MASK 0:NOT MASK */ + max77705->irq_masks_cur[i] = 0xff; + max77705->irq_masks_cache[i] = 0xff; + + i2c = get_i2c(max77705, i); + + if (IS_ERR_OR_NULL(i2c)) + continue; + if (max77705_mask_reg[i] == MAX77705_REG_INVALID) + continue; + regmap_write(i2c, max77705_mask_reg[i], 0xff); + } + + /* Register with genirq */ + for (i = 0; i < MAX77705_IRQ_NR; i++) { + cur_irq = i + max77705->irq_base; + irq_set_chip_data(cur_irq, max77705); + irq_set_chip_and_handler(cur_irq, &max77705_irq_chip, + handle_level_irq); + irq_set_nested_thread(cur_irq, 1); +#ifdef CONFIG_ARM + set_irq_flags(cur_irq, IRQF_VALID); +#else + irq_set_noprobe(cur_irq); +#endif + } + + ret = max77705_irq_mask_subdevice(max77705, MAX77705_IRQSRC_CHG | MAX77705_IRQSRC_TOP | + MAX77705_IRQSRC_FG | MAX77705_IRQSRC_USBC); + if (ret) { + dev_err(max77705->dev, "Failed to mask subdevice irqs\n"); + return ret; + } + + ret = devm_request_threaded_irq(max77705->dev, max77705->irq, NULL, max77705_irq_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "max77705-irq", max77705); + if (ret) { + dev_err(max77705->dev, "Failed to request IRQ %d: %d\n", + max77705->irq, ret); + } + + return ret; +} + +MODULE_LICENSE("GPL"); diff --git a/include/linux/mfd/max77705-private.h b/include/linux/mfd/max77705-private.h new file mode 100644 index 000000000000..bd6ab9c582b8 --- /dev/null +++ b/include/linux/mfd/max77705-private.h @@ -0,0 +1,281 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * max77705-private.h + * + * 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. + */ + +#ifndef __LINUX_MFD_MAX77705_PRIV_H +#define __LINUX_MFD_MAX77705_PRIV_H + +#include +#include +#include +#include +#include +#include + +#define MAX77705_REG_INVALID (0xff) +#define MAX77705_IRQSRC_CHG BIT(0) +#define MAX77705_IRQSRC_TOP BIT(1) +#define MAX77705_IRQSRC_FG BIT(2) +#define MAX77705_IRQSRC_USBC BIT(3) + +/* STATUS_REG */ +#define MAX77705_BAT_ABSENT_MASK BIT(3) +/* MAX77705 MAINCTRL1 register */ +#define MAX77705_MAINCTRL1_BIASEN_SHIFT 7 +#define MAX77705_MAINCTRL1_BIASEN_MASK BIT(MAX77705_MAINCTRL1_BIASEN_SHIFT) + +/* max77705-haptic configuration register */ +#define MAX77705_CONFIG2_MEN_SHIFT 6 +#define MAX77705_CONFIG2_MODE_SHIFT 7 +#define MAX77705_CONFIG2_HTYP_SHIFT 5 + +#define MAX77705_REVISION_MASK 7 +#define MAX77705_VERSION_MASK 0xF8 +#define MAX77705_VERSION_SHIFT 3 + +enum max77705_hw_rev { + MAX77705_PASS1 = 1, + MAX77705_PASS2, + MAX77705_PASS3, +}; + +enum max77705_reg { + /* Slave addr = 0xCC */ + /* PMIC Top-Level Registers */ + MAX77705_PMIC_REG_PMICID1 = 0x00, + MAX77705_PMIC_REG_PMICREV = 0x01, + MAX77705_PMIC_REG_MAINCTRL1 = 0x02, + MAX77705_PMIC_REG_INTSRC = 0x22, + MAX77705_PMIC_REG_INTSRC_MASK = 0x23, + MAX77705_PMIC_REG_SYSTEM_INT = 0x24, + MAX77705_PMIC_REG_RESERVED_25 = 0x25, + MAX77705_PMIC_REG_SYSTEM_INT_MASK = 0x26, + MAX77705_PMIC_REG_RESERVED_27 = 0x27, + MAX77705_PMIC_REG_RESERVED_28 = 0x28, + MAX77705_PMIC_REG_RESERVED_29 = 0x29, + MAX77705_PMIC_REG_BOOSTCONTROL1 = 0x4C, + MAX77705_PMIC_REG_BSTOUT_MASK = 0x03, + MAX77705_PMIC_REG_BOOSTCONTROL2 = 0x4F, + MAX77705_PMIC_REG_FORCE_EN_MASK = 0x08, + MAX77705_PMIC_REG_SW_RESET = 0x50, + MAX77705_PMIC_REG_USBC_RESET = 0x51, + + /* Haptic motor driver Registers */ + MAX77705_PMIC_REG_MCONFIG = 0x10, + MAX77705_PMIC_REG_MCONFIG2 = 0x11, + + MAX77705_CHG_REG_INT = 0xB0, + MAX77705_CHG_REG_INT_MASK = 0xB1, + MAX77705_CHG_REG_INT_OK = 0xB2, + MAX77705_CHG_REG_DETAILS_00 = 0xB3, + MAX77705_CHG_REG_DETAILS_01 = 0xB4, + MAX77705_CHG_REG_DETAILS_02 = 0xB5, + MAX77705_CHG_REG_DTLS_03 = 0xB6, + MAX77705_CHG_REG_CNFG_00 = 0xB7, + MAX77705_CHG_REG_CNFG_01 = 0xB8, + MAX77705_CHG_REG_CNFG_02 = 0xB9, + MAX77705_CHG_REG_CNFG_03 = 0xBA, + MAX77705_CHG_REG_CNFG_04 = 0xBB, + MAX77705_CHG_REG_CNFG_05 = 0xBC, + MAX77705_CHG_REG_CNFG_06 = 0xBD, + MAX77705_CHG_REG_CNFG_07 = 0xBE, + MAX77705_CHG_REG_CNFG_08 = 0xBF, + MAX77705_CHG_REG_CNFG_09 = 0xC0, + MAX77705_CHG_REG_CNFG_10 = 0xC1, + MAX77705_CHG_REG_CNFG_11 = 0xC2, + MAX77705_CHG_REG_CNFG_12 = 0xC3, + MAX77705_CHG_REG_CNFG_13 = 0xC4, + MAX77705_CHG_REG_CNFG_14 = 0xC5, + MAX77705_CHG_REG_SAFEOUT_CTRL = 0xC6, + + MAX77705_PMIC_REG_END, +}; + +enum max77705_charger_battery_state { + MAX77705_BATTERY_NOBAT, + MAX77705_BATTERY_PREQUALIFICATION, + MAX77705_BATTERY_DEAD, + MAX77705_BATTERY_GOOD, + MAX77705_BATTERY_LOWVOLTAGE, + MAX77705_BATTERY_OVERVOLTAGE, + MAX77705_BATTERY_RESERVED, +}; + +enum max77705_charger_charge_type { + MAX77705_CHARGER_CONSTANT_CURRENT = 1, + MAX77705_CHARGER_CONSTANT_VOLTAGE, + MAX77705_CHARGER_END_OF_CHARGE, + MAX77705_CHARGER_DONE, +}; + +/* Slave addr = 0x6C : Fuelgauge */ +enum max77705_fuelgauge_reg { + STATUS_REG = 0x00, + VALRT_THRESHOLD_REG = 0x01, + TALRT_THRESHOLD_REG = 0x02, + SALRT_THRESHOLD_REG = 0x03, + REMCAP_REP_REG = 0x05, + SOCREP_REG = 0x06, + TEMPERATURE_REG = 0x08, + VCELL_REG = 0x09, + TIME_TO_EMPTY_REG = 0x11, + FULLSOCTHR_REG = 0x13, + CURRENT_REG = 0x0A, + AVG_CURRENT_REG = 0x0B, + SOCMIX_REG = 0x0D, + SOCAV_REG = 0x0E, + REMCAP_MIX_REG = 0x0F, + FULLCAP_REG = 0x10, + RFAST_REG = 0x15, + AVR_TEMPERATURE_REG = 0x16, + CYCLES_REG = 0x17, + DESIGNCAP_REG = 0x18, + AVR_VCELL_REG = 0x19, + TIME_TO_FULL_REG = 0x20, + CONFIG_REG = 0x1D, + ICHGTERM_REG = 0x1E, + REMCAP_AV_REG = 0x1F, + FULLCAP_NOM_REG = 0x23, + LEARN_CFG_REG = 0x28, + FILTER_CFG_REG = 0x29, + MISCCFG_REG = 0x2B, + QRTABLE20_REG = 0x32, + FULLCAP_REP_REG = 0x35, + RCOMP_REG = 0x38, + VEMPTY_REG = 0x3A, + FSTAT_REG = 0x3D, + DISCHARGE_THRESHOLD_REG = 0x40, + QRTABLE30_REG = 0x42, + ISYS_REG = 0x43, + DQACC_REG = 0x45, + DPACC_REG = 0x46, + AVGISYS_REG = 0x4B, + QH_REG = 0x4D, + VSYS_REG = 0xB1, + TALRTTH2_REG = 0xB2, + /* "not used REG(0xB2)" is for checking fuelgague init result. */ + FG_INIT_RESULT_REG = TALRTTH2_REG, + VBYP_REG = 0xB3, + CONFIG2_REG = 0xBB, + IIN_REG = 0xD0, + OCV_REG = 0xEE, + VFOCV_REG = 0xFB, + VFSOC_REG = 0xFF, + + MAX77705_FG_END, +}; + +enum max77705_irq_source { + SYS_INT = 0, + CHG_INT, + FUEL_INT, + MAX77705_IRQ_GROUP_NR, +}; + + +#define MAX77705_REG_MAINCTRL1_BIASEN BIT(7) + +/* Slave addr = 0x94: RGB LED */ +enum max77705_led_reg { + MAX77705_RGBLED_REG_LEDEN = 0x30, + MAX77705_RGBLED_REG_LED0BRT = 0x31, + MAX77705_RGBLED_REG_LED1BRT = 0x32, + MAX77705_RGBLED_REG_LED2BRT = 0x33, + MAX77705_RGBLED_REG_LED3BRT = 0x34, + MAX77705_RGBLED_REG_LEDRMP = 0x36, + MAX77705_RGBLED_REG_LEDBLNK = 0x38, + MAX77705_LED_REG_END, +}; + +enum max77705_irq { + /* PMIC; TOPSYS */ + MAX77705_SYSTEM_IRQ_BSTEN_INT, + MAX77705_SYSTEM_IRQ_SYSUVLO_INT, + MAX77705_SYSTEM_IRQ_SYSOVLO_INT, + MAX77705_SYSTEM_IRQ_TSHDN_INT, + MAX77705_SYSTEM_IRQ_TM_INT, + + /* PMIC; Charger */ + MAX77705_CHG_IRQ_BYP_I, + MAX77705_CHG_IRQ_BAT_I, + MAX77705_CHG_IRQ_CHG_I, + MAX77705_CHG_IRQ_WCIN_I, + MAX77705_CHG_IRQ_CHGIN_I, + MAX77705_CHG_IRQ_AICL_I, + + /* Fuelgauge */ + MAX77705_FG_IRQ_ALERT, + + MAX77705_IRQ_NR, +}; + +struct max77705_dev { + struct device *dev; + struct i2c_client *i2c; /* 0xCC; Haptic, PMIC */ + struct i2c_client *charger; /* 0xD2; Charger */ + struct i2c_client *fuelgauge; /* 0x6C; Fuelgauge */ + struct i2c_client *muic; /* 0x4A; MUIC */ + struct i2c_client *debug; /* 0xC4; Debug */ + struct mutex i2c_lock; + + struct regmap *regmap; + struct regmap *regmap_fg; + struct regmap *regmap_charger; + struct regmap *regmap_leds; + + int type; + + int irq; + int irq_base; + int irq_masks_cur[MAX77705_IRQ_GROUP_NR]; + int irq_masks_cache[MAX77705_IRQ_GROUP_NR]; + bool wakeup; + struct mutex irqlock; + +#ifdef CONFIG_HIBERNATION + /* For hibernation */ + u8 reg_pmic_dump[MAX77705_PMIC_REG_END]; + u8 reg_muic_dump[MAX77705_USBC_REG_END]; + u8 reg_led_dump[MAX77705_LED_REG_END]; +#endif + + /* pmic VER/REV register */ + u8 pmic_rev; /* pmic Rev */ + u8 pmic_ver; /* pmic version */ + + u8 cc_booting_complete; + + wait_queue_head_t queue_empty_wait_q; + int doing_irq; + int is_usbc_queue; + + struct max77705_platform_data *pdata; +}; + +enum max77705_types { + TYPE_MAX77705, +}; + + +/** + * Unmask sub device interrupts on device level + * + * @param max77705 - device structure + * @param mask - sub device interrupts to unmask + */ +extern inline int max77705_irq_unmask_subdevice(struct max77705_dev *max77705, unsigned int mask); + +/** + * Same as max77705_irq_unmask_device, but for masking. + */ +extern inline int max77705_irq_mask_subdevice(struct max77705_dev *max77705, unsigned int mask); + + +extern int max77705_irq_init(struct max77705_dev *max77705); + +#endif /* __LINUX_MFD_MAX77705_PRIV_H */ diff --git a/include/linux/mfd/max77705.h b/include/linux/mfd/max77705.h new file mode 100644 index 000000000000..e2d757c77973 --- /dev/null +++ b/include/linux/mfd/max77705.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * max77705.h - Driver for the Maxim 77705 + * + * 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. + */ + +#ifndef __MAX77705_H__ +#define __MAX77705_H__ + +#define MFD_DEV_NAME "max77705" + +struct max77705_platform_data { + struct power_supply_battery_info *bat_info; +}; + +#endif /* __MAX77705_H__ */ +