Jean-Jacques Hiblot | 53509ec | 2020-10-16 16:16:32 +0530 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * MMIO register bitfield-controlled multiplexer driver |
| 4 | * Based on the linux mmio multiplexer driver |
| 5 | * |
| 6 | * Copyright (C) 2017 Pengutronix, Philipp Zabel <kernel@pengutronix.de> |
| 7 | * Copyright (C) 2019 Texas Instrument, Jean-jacques Hiblot <jjhiblot@ti.com> |
| 8 | */ |
| 9 | #include <common.h> |
| 10 | #include <dm.h> |
| 11 | #include <mux-internal.h> |
| 12 | #include <regmap.h> |
| 13 | #include <syscon.h> |
| 14 | #include <dm/device.h> |
Simon Glass | 9558862 | 2020-12-22 19:30:28 -0700 | [diff] [blame] | 15 | #include <dm/device-internal.h> |
Jean-Jacques Hiblot | 53509ec | 2020-10-16 16:16:32 +0530 | [diff] [blame] | 16 | #include <dm/device_compat.h> |
| 17 | #include <dm/read.h> |
| 18 | #include <dm/devres.h> |
| 19 | #include <dt-bindings/mux/mux.h> |
| 20 | #include <linux/bitops.h> |
| 21 | |
| 22 | static int mux_mmio_set(struct mux_control *mux, int state) |
| 23 | { |
| 24 | struct regmap_field **fields = dev_get_priv(mux->dev); |
| 25 | |
| 26 | return regmap_field_write(fields[mux_control_get_index(mux)], state); |
| 27 | } |
| 28 | |
| 29 | static const struct mux_control_ops mux_mmio_ops = { |
| 30 | .set = mux_mmio_set, |
| 31 | }; |
| 32 | |
| 33 | static const struct udevice_id mmio_mux_of_match[] = { |
| 34 | { .compatible = "mmio-mux" }, |
| 35 | { /* sentinel */ }, |
| 36 | }; |
| 37 | |
| 38 | static int mmio_mux_probe(struct udevice *dev) |
| 39 | { |
| 40 | struct regmap_field **fields; |
| 41 | struct mux_chip *mux_chip = dev_get_uclass_priv(dev); |
| 42 | struct regmap *regmap; |
| 43 | u32 *mux_reg_masks; |
| 44 | u32 *idle_states; |
| 45 | int num_fields; |
| 46 | int ret; |
| 47 | int i; |
| 48 | |
| 49 | regmap = syscon_node_to_regmap(dev_ofnode(dev->parent)); |
| 50 | if (IS_ERR(regmap)) { |
| 51 | ret = PTR_ERR(regmap); |
| 52 | dev_err(dev, "failed to get regmap: %d\n", ret); |
| 53 | return ret; |
| 54 | } |
| 55 | |
| 56 | num_fields = dev_read_size(dev, "mux-reg-masks"); |
| 57 | if (num_fields < 0) |
| 58 | return log_msg_ret("mux-reg-masks missing", -EINVAL); |
| 59 | |
| 60 | num_fields /= sizeof(u32); |
| 61 | if (num_fields == 0 || num_fields % 2) |
| 62 | ret = -EINVAL; |
| 63 | num_fields = num_fields / 2; |
| 64 | |
| 65 | ret = mux_alloc_controllers(dev, num_fields); |
| 66 | if (ret < 0) |
| 67 | return log_msg_ret("mux_alloc_controllers", ret); |
| 68 | |
| 69 | fields = devm_kmalloc(dev, num_fields * sizeof(*fields), __GFP_ZERO); |
| 70 | if (!fields) |
| 71 | return -ENOMEM; |
Simon Glass | 9558862 | 2020-12-22 19:30:28 -0700 | [diff] [blame] | 72 | dev_set_priv(dev, fields); |
Jean-Jacques Hiblot | 53509ec | 2020-10-16 16:16:32 +0530 | [diff] [blame] | 73 | |
| 74 | mux_reg_masks = devm_kmalloc(dev, num_fields * 2 * sizeof(u32), |
| 75 | __GFP_ZERO); |
| 76 | if (!mux_reg_masks) |
| 77 | return -ENOMEM; |
| 78 | |
| 79 | ret = dev_read_u32_array(dev, "mux-reg-masks", mux_reg_masks, |
| 80 | num_fields * 2); |
| 81 | if (ret < 0) |
| 82 | return log_msg_ret("mux-reg-masks read", ret); |
| 83 | |
| 84 | idle_states = devm_kmalloc(dev, num_fields * sizeof(u32), __GFP_ZERO); |
| 85 | if (!idle_states) |
| 86 | return -ENOMEM; |
| 87 | |
| 88 | ret = dev_read_u32_array(dev, "idle-states", idle_states, num_fields); |
| 89 | if (ret < 0) { |
| 90 | log_err("idle-states"); |
| 91 | devm_kfree(dev, idle_states); |
| 92 | idle_states = NULL; |
| 93 | } |
| 94 | |
| 95 | for (i = 0; i < num_fields; i++) { |
| 96 | struct mux_control *mux = &mux_chip->mux[i]; |
| 97 | struct reg_field field; |
| 98 | u32 reg, mask; |
| 99 | int bits; |
| 100 | |
| 101 | reg = mux_reg_masks[2 * i]; |
| 102 | mask = mux_reg_masks[2 * i + 1]; |
| 103 | |
| 104 | field.reg = reg; |
| 105 | field.msb = fls(mask) - 1; |
| 106 | field.lsb = ffs(mask) - 1; |
| 107 | |
| 108 | if (mask != GENMASK(field.msb, field.lsb)) |
| 109 | return log_msg_ret("invalid mask", -EINVAL); |
| 110 | |
| 111 | fields[i] = devm_regmap_field_alloc(dev, regmap, field); |
| 112 | if (IS_ERR(fields[i])) { |
| 113 | ret = PTR_ERR(fields[i]); |
| 114 | return log_msg_ret("regmap_field_alloc", ret); |
| 115 | } |
| 116 | |
| 117 | bits = 1 + field.msb - field.lsb; |
| 118 | mux->states = 1 << bits; |
| 119 | |
| 120 | if (!idle_states) |
| 121 | continue; |
| 122 | |
| 123 | if (idle_states[i] != MUX_IDLE_AS_IS && |
| 124 | idle_states[i] >= mux->states) |
| 125 | return log_msg_ret("idle-states range", -EINVAL); |
| 126 | |
| 127 | mux->idle_state = idle_states[i]; |
| 128 | } |
| 129 | |
| 130 | devm_kfree(dev, mux_reg_masks); |
| 131 | if (idle_states) |
| 132 | devm_kfree(dev, idle_states); |
| 133 | |
| 134 | return 0; |
| 135 | } |
| 136 | |
| 137 | U_BOOT_DRIVER(mmio_mux) = { |
| 138 | .name = "mmio-mux", |
| 139 | .id = UCLASS_MUX, |
| 140 | .of_match = mmio_mux_of_match, |
| 141 | .probe = mmio_mux_probe, |
| 142 | .ops = &mux_mmio_ops, |
| 143 | }; |