I'm currently working of a driver for the onoff block of the max77620 for the nintendo switches powerkey basing my work off of https://datasheets.maximintegrated.com/en/ds/MAX77714.pdf which seems to be a similar chip. However I don't seem to ever get the rising interrupt, just the falling one and i get that only once. Another issue i had is that the mask register is at a different offset to the top mask register than the status is to the top status register, i had to hack a mask_offset property to regmap-irq to fix this. If anyone here with access to the real datasheet can confirm if my code is correct that would be very helpful :) Attached below is my diff: -------------- diff --git a/arch/arm64/boot/dts/nvidia/tegra210-nintendo-switch.dts b/arch/arm64/boot/dts/nvidia/tegra210-nintendo-switch.dts index bfe4f4b1e091..19d189b58d91 100644 --- a/arch/arm64/boot/dts/nvidia/tegra210-nintendo-switch.dts +++ b/arch/arm64/boot/dts/nvidia/tegra210-nintendo-switch.dts @@ -1656,7 +1656,7 @@ max77620: max77620@3c { compatible = "maxim,max77620"; reg = <0x3c>; - interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>; + interrupts = <GIC_SPI 86 IRQ_TYPE_LEVEL_LOW>; #interrupt-cells = <2>; interrupt-controller; @@ -1945,8 +1945,8 @@ max77621_cpu: pmic@1b { compatible = "maxim,max77621"; reg = <0x1b>; - interrupt-parent = <&gpio>; - interrupts = <TEGRA_GPIO(K, 6) IRQ_TYPE_LEVEL_LOW>; + interrupt-parent = <&gpio>; + interrupts = <TEGRA_GPIO(K, 6) IRQ_TYPE_LEVEL_LOW>; regulator-name = "PPVAR_CPU"; // smaug: 800000..1231250 regulator-min-microvolt = <800000>; @@ -1969,8 +1969,8 @@ max77621_gpu: pmic@1c { compatible = "maxim,max77621"; reg = <0x1c>; - interrupt-parent = <&gpio>; - interrupts = <TEGRA_GPIO(K, 7) IRQ_TYPE_LEVEL_LOW>; + interrupt-parent = <&gpio>; + interrupts = <TEGRA_GPIO(K, 7) IRQ_TYPE_LEVEL_LOW>; regulator-name = "PPVAR_GPU"; regulator-min-microvolt = <840000>; regulator-max-microvolt = <1150000>; diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 330c1f7e9665..45f61660e1b4 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -240,7 +240,10 @@ static void regmap_irq_enable(struct irq_data *data) if (d->chip->clear_on_unmask) d->clear_status = true; - d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~mask; + if (!irq_data->mask_offset) + d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~mask; + else + d->mask_buf[irq_data->mask_offset / map->reg_stride] &= ~mask; } static void regmap_irq_disable(struct irq_data *data) @@ -249,7 +252,10 @@ static void regmap_irq_disable(struct irq_data *data) struct regmap *map = d->map; const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq); - d->mask_buf[irq_data->reg_offset / map->reg_stride] |= irq_data->mask; + if (!irq_data->mask_offset) + d->mask_buf[irq_data->reg_offset / map->reg_stride] |= irq_data->mask; + else + d->mask_buf[irq_data->mask_offset / map->reg_stride] |= irq_data->mask; } static int regmap_irq_set_type(struct irq_data *data, unsigned int type) me@archlinux ~/Development/tegra/linux switch-5.0 ●✚ git diff HEAD drivers/ diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 330c1f7e9665..45f61660e1b4 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -240,7 +240,10 @@ static void regmap_irq_enable(struct irq_data *data) if (d->chip->clear_on_unmask) d->clear_status = true; - d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~mask; + if (!irq_data->mask_offset) + d->mask_buf[irq_data->reg_offset / map->reg_stride] &= ~mask; + else + d->mask_buf[irq_data->mask_offset / map->reg_stride] &= ~mask; } static void regmap_irq_disable(struct irq_data *data) @@ -249,7 +252,10 @@ static void regmap_irq_disable(struct irq_data *data) struct regmap *map = d->map; const struct regmap_irq *irq_data = irq_to_regmap_irq(d, data->hwirq); - d->mask_buf[irq_data->reg_offset / map->reg_stride] |= irq_data->mask; + if (!irq_data->mask_offset) + d->mask_buf[irq_data->reg_offset / map->reg_stride] |= irq_data->mask; + else + d->mask_buf[irq_data->mask_offset / map->reg_stride] |= irq_data->mask; } static int regmap_irq_set_type(struct irq_data *data, unsigned int type) @@ -585,8 +591,10 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, mutex_init(&d->lock); for (i = 0; i < chip->num_irqs; i++) - d->mask_buf_def[chip->irqs[i].reg_offset / map->reg_stride] - |= chip->irqs[i].mask; + if (!chip->irqs[i].mask_offset) + d->mask_buf_def[chip->irqs[i].reg_offset / map->reg_stride] |= chip->irqs[i].mask; + else + d->mask_buf_def[chip->irqs[i].mask_offset / map->reg_stride] |= chip->irqs[i].mask; /* Mask all the interrupts by default */ for (i = 0; i < chip->num_regs; i++) { diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index ca59a2be9bc5..1cac5eb0aad1 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -191,6 +191,17 @@ config INPUT_MAX77693_HAPTIC To compile this driver as module, choose M here: the module will be called max77693-haptic. +config INPUT_MAX77620_ONOFF + tristate "MAX77620 ONOFF support" + depends on MFD_MAX77620 + help + Support the ONOFF block of MAX77620 PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called max77620_onoff. + + config INPUT_MAX8925_ONKEY tristate "MAX8925 ONKEY support" depends on MFD_MAX8925 diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 9d0f9d1ff68f..47f0eaf8b370 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -44,6 +44,7 @@ obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.o +obj-$(CONFIG_INPUT_MAX77620_ONOFF) += max77620-onoff.o obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o diff --git a/drivers/input/misc/max77620-onoff.c b/drivers/input/misc/max77620-onoff.c new file mode 100644 index 000000000000..932536bc5680 --- /dev/null +++ b/drivers/input/misc/max77620-onoff.c @@ -0,0 +1,149 @@ +/**re-- + * MAX77620 ONKEY driver + *ore-- + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@xxxxxxxxxxx> + *ore-- + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + *ore-- + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + *ore-- + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */re-- + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/irq.h> +#include <linux/of.h> +#include <linux/interrupt.h> +#include <linux/regmap.h> +#include <linux/mfd/max77620.h> +#include <linux/slab.h> +#include <linux/device.h> + +#define POWERKEY_PRESSED (1 << 2) /* 0/1 -- up/down */ + +struct max77620_onoff_info { + struct input_dev *input; + struct regmap *regmap; + unsigned int code; +}; + +/* + * MAX77620 gives us an interrupt when ONKEY is pressed or released. + * max77620_set_bits() operates I2C bus and may sleep. So implement + * it in thread IRQ handler. + */ +static irqreturn_t max77620_onoff_handler(int irq, void *data) +{ + struct max77620_onoff_info *info = data; + u32 state; + + regmap_read(info->regmap, MAX77620_REG_ONOFFSTAT, &state); + + input_report_key(info->input, info->code, state & POWERKEY_PRESSED); + input_sync(info->input); + + return IRQ_HANDLED; +} + +static int max77620_onoff_probe(struct platform_device *pdev) +{ + struct max77620_onoff_info *info; + int irq[2], error; + + irq[0] = platform_get_irq(pdev, 0); + if (irq[0] < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + irq[1] = platform_get_irq(pdev, 1); + if (irq[1] < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + info = devm_kzalloc(&pdev->dev, sizeof(struct max77620_onoff_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!info->regmap) { + dev_err(&pdev->dev, "Failed to get parent regmap\n"); + return -ENODEV; + } + + error = device_property_read_u32(&pdev->dev, "linux,code", &info->code); + if (error) + info->code = KEY_POWER; + + info->input = devm_input_allocate_device(&pdev->dev); + if (!info->input) + return -ENOMEM; + + info->input->name = "max77620_onoff"; + info->input->phys = "max77620_onoff/input0"; + info->input->id.bustype = BUS_I2C; + info->input->dev.parent = &pdev->dev; + input_set_capability(info->input, EV_KEY, info->code); + + error = devm_request_threaded_irq(&pdev->dev, irq[0], NULL, + max77620_onoff_handler, IRQF_ONESHOT, + "en0-up", info); + if (error < 0) { + dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n", + irq[0], error); + return error; + } + + error = devm_request_threaded_irq(&pdev->dev, irq[1], NULL, + max77620_onoff_handler, IRQF_ONESHOT, + "en0-down", info); + if (error < 0) { + dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n", + irq[1], error); + return error; + } + + error = input_register_device(info->input); + if (error) { + dev_err(&pdev->dev, "Can't register input device: %d\n", error); + return error; + } + + platform_set_drvdata(pdev, info); +// device_init_wakeup(&pdev->dev, 1); + + return 0; +} + +static int max77620_onoff_remove(struct platform_device *pdev) +{ + device_init_wakeup(&pdev->dev, 0); + + return 0; +} + +static struct platform_driver max77620_onoff_driver = { + .driver = { + .name = "max77620-onoff", + }, + .probe = max77620_onoff_probe, + .remove = max77620_onoff_remove, +}; +module_platform_driver(max77620_onoff_driver); + +MODULE_DESCRIPTION("Maxim MAX77620 ONOFF driver"); +MODULE_AUTHOR("Billy Laws <blaws05@xxxxxxxxx>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/max77620.c b/drivers/mfd/max77620.c index d8ddd1a6f304..ef908af95a80 100644 --- a/drivers/mfd/max77620.c +++ b/drivers/mfd/max77620.c @@ -36,6 +36,12 @@ #include <linux/of_device.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/delay.h> + +static const struct resource onoff_resources[] = { + DEFINE_RES_IRQ(MAX77620_IRQ_ONOFF_EN0_R), + DEFINE_RES_IRQ(MAX77620_IRQ_ONOFF_EN0_F), +}; static const struct resource gpio_resources[] = { DEFINE_RES_IRQ(MAX77620_IRQ_TOP_GPIO), @@ -65,14 +71,21 @@ static const struct regmap_irq max77620_top_irqs[] = { REGMAP_IRQ_REG(MAX77620_IRQ_LBT_MBATLOW, 1, MAX77620_IRQ_LBM_MASK), REGMAP_IRQ_REG(MAX77620_IRQ_LBT_TJALRM1, 1, MAX77620_IRQ_TJALRM1_MASK), REGMAP_IRQ_REG(MAX77620_IRQ_LBT_TJALRM2, 1, MAX77620_IRQ_TJALRM2_MASK), + REGMAP_IRQ_REG2(MAX77620_IRQ_ONOFF_EN0_R, 6, 5, MAX77620_IRQ_ONOFF_EN0_R_MASK), + REGMAP_IRQ_REG2(MAX77620_IRQ_ONOFF_EN0_F, 6, 5, MAX77620_IRQ_ONOFF_EN0_F_MASK), }; + static const struct mfd_cell max77620_children[] = { { .name = "max77620-pinctrl", }, { .name = "max77620-clock", }, { .name = "max77620-pmic", }, { .name = "max77620-watchdog", }, { + .name = "max77620-onoff", + .resources = onoff_resources, + .num_resources = ARRAY_SIZE(onoff_resources), + }, { .name = "max77620-gpio", .resources = gpio_resources, .num_resources = ARRAY_SIZE(gpio_resources), @@ -183,12 +196,19 @@ static int max77620_irq_global_mask(void *irq_drv_data) { struct max77620_chip *chip = irq_drv_data; int ret; + unsigned int val; ret = regmap_update_bits(chip->rmap, MAX77620_REG_INTENLBT, MAX77620_GLBLM_MASK, MAX77620_GLBLM_MASK); if (ret < 0) dev_err(chip->dev, "Failed to set GLBLM: %d\n", ret); + regmap_read(chip->rmap, MAX77620_REG_IRQTOP, &val); + dev_err(chip->dev, "1top status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQ, &val); + dev_err(chip->dev, "status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQM, &val); + dev_err(chip->dev, "mask reg %x", val); return ret; } @@ -196,12 +216,19 @@ static int max77620_irq_global_unmask(void *irq_drv_data) { struct max77620_chip *chip = irq_drv_data; int ret; + unsigned int val; ret = regmap_update_bits(chip->rmap, MAX77620_REG_INTENLBT, MAX77620_GLBLM_MASK, 0); if (ret < 0) dev_err(chip->dev, "Failed to reset GLBLM: %d\n", ret); + regmap_read(chip->rmap, MAX77620_REG_IRQTOP, &val); + dev_err(chip->dev, "1top status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQ, &val); + dev_err(chip->dev, "1status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQM, &val); + dev_err(chip->dev, "1mask reg %x", val); return ret; } @@ -209,7 +236,7 @@ static struct regmap_irq_chip max77620_top_irq_chip = { .name = "max77620-top", .irqs = max77620_top_irqs, .num_irqs = ARRAY_SIZE(max77620_top_irqs), - .num_regs = 2, + .num_regs = 7, .status_base = MAX77620_REG_IRQTOP, .mask_base = MAX77620_REG_IRQTOPM, .handle_pre_irq = max77620_irq_global_mask, @@ -431,6 +458,7 @@ static int max77620_probe(struct i2c_client *client, const struct mfd_cell *mfd_cells; int n_mfd_cells; int ret; + unsigned int val; chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) @@ -478,7 +506,7 @@ static int max77620_probe(struct i2c_client *client, dev_err(chip->dev, "Failed to add regmap irq: %d\n", ret); return ret; } - + ret = max77620_initialise_fps(chip); if (ret < 0) return ret; @@ -491,6 +519,34 @@ static int max77620_probe(struct i2c_client *client, return ret; } + regmap_read(chip->rmap, MAX77620_REG_IRQTOP, &val); + dev_err(chip->dev, "2top status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQ, &val); + dev_err(chip->dev, "1status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQM, &val); + dev_err(chip->dev, "1mask reg %x", val); + + ret = regmap_update_bits(chip->rmap, MAX77620_REG_INTENLBT, + MAX77620_GLBLM_MASK, MAX77620_GLBLM_MASK); + if (ret < 0) + dev_err(chip->dev, "Failed to set GLBLM: %d\n", ret); + + udelay(1000); + ret = regmap_update_bits(chip->rmap, MAX77620_REG_INTENLBT, + MAX77620_GLBLM_MASK, 0); + if (ret < 0) + dev_err(chip->dev, "Failed to set GLBLM: %d\n", ret); + + regmap_read(chip->rmap, MAX77620_REG_IRQTOP, &val); + dev_err(chip->dev, "2top status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQ, &val); + dev_err(chip->dev, "1status reg %x", val); + regmap_read(chip->rmap, MAX77620_REG_ONOFFIRQM, &val); + dev_err(chip->dev, "1mask reg %x", val); + + + + return 0; }