| 1 | // SPDX-License-Identifier: GPL-2.0-only |
| 2 | /* |
| 3 | * Maxim MAX77714 Core Driver |
| 4 | * |
| 5 | * Copyright (C) 2022 Luca Ceresoli |
| 6 | * Author: Luca Ceresoli <luca.ceresoli@bootlin.com> |
| 7 | */ |
| 8 | |
| 9 | #include <linux/i2c.h> |
| 10 | #include <linux/interrupt.h> |
| 11 | #include <linux/mfd/core.h> |
| 12 | #include <linux/mfd/max77714.h> |
| 13 | #include <linux/module.h> |
| 14 | #include <linux/of.h> |
| 15 | #include <linux/regmap.h> |
| 16 | |
| 17 | static const struct mfd_cell max77714_cells[] = { |
| 18 | { .name = "max77714-watchdog" }, |
| 19 | { .name = "max77714-rtc" }, |
| 20 | }; |
| 21 | |
| 22 | static const struct regmap_range max77714_readable_ranges[] = { |
| 23 | regmap_reg_range(MAX77714_INT_TOP, MAX77714_INT_TOP), |
| 24 | regmap_reg_range(MAX77714_INT_TOPM, MAX77714_INT_TOPM), |
| 25 | regmap_reg_range(MAX77714_32K_STATUS, MAX77714_32K_CONFIG), |
| 26 | regmap_reg_range(MAX77714_CNFG_GLBL2, MAX77714_CNFG2_ONOFF), |
| 27 | }; |
| 28 | |
| 29 | static const struct regmap_range max77714_writable_ranges[] = { |
| 30 | regmap_reg_range(MAX77714_INT_TOPM, MAX77714_INT_TOPM), |
| 31 | regmap_reg_range(MAX77714_32K_CONFIG, MAX77714_32K_CONFIG), |
| 32 | regmap_reg_range(MAX77714_CNFG_GLBL2, MAX77714_CNFG2_ONOFF), |
| 33 | }; |
| 34 | |
| 35 | static const struct regmap_access_table max77714_readable_table = { |
| 36 | .yes_ranges = max77714_readable_ranges, |
| 37 | .n_yes_ranges = ARRAY_SIZE(max77714_readable_ranges), |
| 38 | }; |
| 39 | |
| 40 | static const struct regmap_access_table max77714_writable_table = { |
| 41 | .yes_ranges = max77714_writable_ranges, |
| 42 | .n_yes_ranges = ARRAY_SIZE(max77714_writable_ranges), |
| 43 | }; |
| 44 | |
| 45 | static const struct regmap_config max77714_regmap_config = { |
| 46 | .reg_bits = 8, |
| 47 | .val_bits = 8, |
| 48 | .max_register = MAX77714_CNFG2_ONOFF, |
| 49 | .rd_table = &max77714_readable_table, |
| 50 | .wr_table = &max77714_writable_table, |
| 51 | }; |
| 52 | |
| 53 | static const struct regmap_irq max77714_top_irqs[] = { |
| 54 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_ONOFF, 0, MAX77714_INT_TOP_ONOFF), |
| 55 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_RTC, 0, MAX77714_INT_TOP_RTC), |
| 56 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_GPIO, 0, MAX77714_INT_TOP_GPIO), |
| 57 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_LDO, 0, MAX77714_INT_TOP_LDO), |
| 58 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_SD, 0, MAX77714_INT_TOP_SD), |
| 59 | REGMAP_IRQ_REG(MAX77714_IRQ_TOP_GLBL, 0, MAX77714_INT_TOP_GLBL), |
| 60 | }; |
| 61 | |
| 62 | static const struct regmap_irq_chip max77714_irq_chip = { |
| 63 | .name = "max77714-pmic" , |
| 64 | .status_base = MAX77714_INT_TOP, |
| 65 | .mask_base = MAX77714_INT_TOPM, |
| 66 | .num_regs = 1, |
| 67 | .irqs = max77714_top_irqs, |
| 68 | .num_irqs = ARRAY_SIZE(max77714_top_irqs), |
| 69 | }; |
| 70 | |
| 71 | /* |
| 72 | * MAX77714 initially uses the internal, low precision oscillator. Enable |
| 73 | * the external oscillator by setting the XOSC_RETRY bit. If the external |
| 74 | * oscillator is not OK (probably not installed) this has no effect. |
| 75 | */ |
| 76 | static int max77714_setup_xosc(struct device *dev, struct regmap *regmap) |
| 77 | { |
| 78 | /* Internal Crystal Load Capacitance, indexed by value of 32KLOAD bits */ |
| 79 | static const unsigned int load_cap[4] = {0, 10, 12, 22}; /* pF */ |
| 80 | unsigned int load_cap_idx; |
| 81 | unsigned int status; |
| 82 | int err; |
| 83 | |
| 84 | err = regmap_update_bits(map: regmap, MAX77714_32K_CONFIG, |
| 85 | MAX77714_32K_CONFIG_XOSC_RETRY, |
| 86 | MAX77714_32K_CONFIG_XOSC_RETRY); |
| 87 | if (err) |
| 88 | return dev_err_probe(dev, err, fmt: "Failed to configure the external oscillator\n" ); |
| 89 | |
| 90 | err = regmap_read(map: regmap, MAX77714_32K_STATUS, val: &status); |
| 91 | if (err) |
| 92 | return dev_err_probe(dev, err, fmt: "Failed to read external oscillator status\n" ); |
| 93 | |
| 94 | load_cap_idx = (status >> MAX77714_32K_STATUS_32KLOAD_SHF) |
| 95 | & MAX77714_32K_STATUS_32KLOAD_MSK; |
| 96 | |
| 97 | dev_info(dev, "Using %s oscillator, %d pF load cap\n" , |
| 98 | status & MAX77714_32K_STATUS_32KSOURCE ? "internal" : "external" , |
| 99 | load_cap[load_cap_idx]); |
| 100 | |
| 101 | return 0; |
| 102 | } |
| 103 | |
| 104 | static int max77714_probe(struct i2c_client *client) |
| 105 | { |
| 106 | struct device *dev = &client->dev; |
| 107 | struct regmap *regmap; |
| 108 | struct regmap_irq_chip_data *irq_data; |
| 109 | int err; |
| 110 | |
| 111 | regmap = devm_regmap_init_i2c(client, &max77714_regmap_config); |
| 112 | if (IS_ERR(ptr: regmap)) |
| 113 | return dev_err_probe(dev, err: PTR_ERR(ptr: regmap), |
| 114 | fmt: "Failed to initialise regmap\n" ); |
| 115 | |
| 116 | err = max77714_setup_xosc(dev, regmap); |
| 117 | if (err) |
| 118 | return err; |
| 119 | |
| 120 | err = devm_regmap_add_irq_chip(dev, map: regmap, irq: client->irq, |
| 121 | IRQF_ONESHOT | IRQF_SHARED, irq_base: 0, |
| 122 | chip: &max77714_irq_chip, data: &irq_data); |
| 123 | if (err) |
| 124 | return dev_err_probe(dev, err, fmt: "Failed to add PMIC IRQ chip\n" ); |
| 125 | |
| 126 | err = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, |
| 127 | cells: max77714_cells, ARRAY_SIZE(max77714_cells), |
| 128 | NULL, irq_base: 0, NULL); |
| 129 | if (err) |
| 130 | return dev_err_probe(dev, err, fmt: "Failed to register child devices\n" ); |
| 131 | |
| 132 | return 0; |
| 133 | } |
| 134 | |
| 135 | static const struct of_device_id max77714_dt_match[] = { |
| 136 | { .compatible = "maxim,max77714" }, |
| 137 | {}, |
| 138 | }; |
| 139 | MODULE_DEVICE_TABLE(of, max77714_dt_match); |
| 140 | |
| 141 | static struct i2c_driver max77714_driver = { |
| 142 | .driver = { |
| 143 | .name = "max77714" , |
| 144 | .of_match_table = max77714_dt_match, |
| 145 | }, |
| 146 | .probe = max77714_probe, |
| 147 | }; |
| 148 | module_i2c_driver(max77714_driver); |
| 149 | |
| 150 | MODULE_DESCRIPTION("Maxim MAX77714 MFD core driver" ); |
| 151 | MODULE_AUTHOR("Luca Ceresoli <luca.ceresoli@bootlin.com>" ); |
| 152 | MODULE_LICENSE("GPL" ); |
| 153 | |