| rjw | 1f88458 | 2022-01-06 17:20:42 +0800 | [diff] [blame] | 1 | /* | 
|  | 2 | * drivers/mfd/si476x-prop.c -- Subroutines to access | 
|  | 3 | * properties of si476x chips | 
|  | 4 | * | 
|  | 5 | * Copyright (C) 2012 Innovative Converged Devices(ICD) | 
|  | 6 | * Copyright (C) 2013 Andrey Smirnov | 
|  | 7 | * | 
|  | 8 | * Author: Andrey Smirnov <andrew.smirnov@gmail.com> | 
|  | 9 | * | 
|  | 10 | * This program is free software; you can redistribute it and/or modify | 
|  | 11 | * it under the terms of the GNU General Public License as published by | 
|  | 12 | * the Free Software Foundation; version 2 of the License. | 
|  | 13 | * | 
|  | 14 | * This program is distributed in the hope that it will be useful, but | 
|  | 15 | * WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | 17 | * General Public License for more details. | 
|  | 18 | */ | 
|  | 19 | #include <linux/module.h> | 
|  | 20 |  | 
|  | 21 | #include <linux/mfd/si476x-core.h> | 
|  | 22 |  | 
|  | 23 | struct si476x_property_range { | 
|  | 24 | u16 low, high; | 
|  | 25 | }; | 
|  | 26 |  | 
|  | 27 | static bool si476x_core_element_is_in_array(u16 element, | 
|  | 28 | const u16 array[], | 
|  | 29 | size_t size) | 
|  | 30 | { | 
|  | 31 | int i; | 
|  | 32 |  | 
|  | 33 | for (i = 0; i < size; i++) | 
|  | 34 | if (element == array[i]) | 
|  | 35 | return true; | 
|  | 36 |  | 
|  | 37 | return false; | 
|  | 38 | } | 
|  | 39 |  | 
|  | 40 | static bool si476x_core_element_is_in_range(u16 element, | 
|  | 41 | const struct si476x_property_range range[], | 
|  | 42 | size_t size) | 
|  | 43 | { | 
|  | 44 | int i; | 
|  | 45 |  | 
|  | 46 | for (i = 0; i < size; i++) | 
|  | 47 | if (element <= range[i].high && element >= range[i].low) | 
|  | 48 | return true; | 
|  | 49 |  | 
|  | 50 | return false; | 
|  | 51 | } | 
|  | 52 |  | 
|  | 53 | static bool si476x_core_is_valid_property_a10(struct si476x_core *core, | 
|  | 54 | u16 property) | 
|  | 55 | { | 
|  | 56 | static const u16 valid_properties[] = { | 
|  | 57 | 0x0000, | 
|  | 58 | 0x0500, 0x0501, | 
|  | 59 | 0x0600, | 
|  | 60 | 0x0709, 0x070C, 0x070D, 0x70E, 0x710, | 
|  | 61 | 0x0718, | 
|  | 62 | 0x1207, 0x1208, | 
|  | 63 | 0x2007, | 
|  | 64 | 0x2300, | 
|  | 65 | }; | 
|  | 66 |  | 
|  | 67 | static const struct si476x_property_range valid_ranges[] = { | 
|  | 68 | { 0x0200, 0x0203 }, | 
|  | 69 | { 0x0300, 0x0303 }, | 
|  | 70 | { 0x0400, 0x0404 }, | 
|  | 71 | { 0x0700, 0x0707 }, | 
|  | 72 | { 0x1100, 0x1102 }, | 
|  | 73 | { 0x1200, 0x1204 }, | 
|  | 74 | { 0x1300, 0x1306 }, | 
|  | 75 | { 0x2000, 0x2005 }, | 
|  | 76 | { 0x2100, 0x2104 }, | 
|  | 77 | { 0x2106, 0x2106 }, | 
|  | 78 | { 0x2200, 0x220E }, | 
|  | 79 | { 0x3100, 0x3104 }, | 
|  | 80 | { 0x3207, 0x320F }, | 
|  | 81 | { 0x3300, 0x3304 }, | 
|  | 82 | { 0x3500, 0x3517 }, | 
|  | 83 | { 0x3600, 0x3617 }, | 
|  | 84 | { 0x3700, 0x3717 }, | 
|  | 85 | { 0x4000, 0x4003 }, | 
|  | 86 | }; | 
|  | 87 |  | 
|  | 88 | return	si476x_core_element_is_in_range(property, valid_ranges, | 
|  | 89 | ARRAY_SIZE(valid_ranges)) || | 
|  | 90 | si476x_core_element_is_in_array(property, valid_properties, | 
|  | 91 | ARRAY_SIZE(valid_properties)); | 
|  | 92 | } | 
|  | 93 |  | 
|  | 94 | static bool si476x_core_is_valid_property_a20(struct si476x_core *core, | 
|  | 95 | u16 property) | 
|  | 96 | { | 
|  | 97 | static const u16 valid_properties[] = { | 
|  | 98 | 0x071B, | 
|  | 99 | 0x1006, | 
|  | 100 | 0x2210, | 
|  | 101 | 0x3401, | 
|  | 102 | }; | 
|  | 103 |  | 
|  | 104 | static const struct si476x_property_range valid_ranges[] = { | 
|  | 105 | { 0x2215, 0x2219 }, | 
|  | 106 | }; | 
|  | 107 |  | 
|  | 108 | return	si476x_core_is_valid_property_a10(core, property) || | 
|  | 109 | si476x_core_element_is_in_range(property, valid_ranges, | 
|  | 110 | ARRAY_SIZE(valid_ranges))  || | 
|  | 111 | si476x_core_element_is_in_array(property, valid_properties, | 
|  | 112 | ARRAY_SIZE(valid_properties)); | 
|  | 113 | } | 
|  | 114 |  | 
|  | 115 | static bool si476x_core_is_valid_property_a30(struct si476x_core *core, | 
|  | 116 | u16 property) | 
|  | 117 | { | 
|  | 118 | static const u16 valid_properties[] = { | 
|  | 119 | 0x071C, 0x071D, | 
|  | 120 | 0x1007, 0x1008, | 
|  | 121 | 0x220F, 0x2214, | 
|  | 122 | 0x2301, | 
|  | 123 | 0x3105, 0x3106, | 
|  | 124 | 0x3402, | 
|  | 125 | }; | 
|  | 126 |  | 
|  | 127 | static const struct si476x_property_range valid_ranges[] = { | 
|  | 128 | { 0x0405, 0x0411 }, | 
|  | 129 | { 0x2008, 0x200B }, | 
|  | 130 | { 0x2220, 0x2223 }, | 
|  | 131 | { 0x3100, 0x3106 }, | 
|  | 132 | }; | 
|  | 133 |  | 
|  | 134 | return	si476x_core_is_valid_property_a20(core, property) || | 
|  | 135 | si476x_core_element_is_in_range(property, valid_ranges, | 
|  | 136 | ARRAY_SIZE(valid_ranges)) || | 
|  | 137 | si476x_core_element_is_in_array(property, valid_properties, | 
|  | 138 | ARRAY_SIZE(valid_properties)); | 
|  | 139 | } | 
|  | 140 |  | 
|  | 141 | typedef bool (*valid_property_pred_t) (struct si476x_core *, u16); | 
|  | 142 |  | 
|  | 143 | static bool si476x_core_is_valid_property(struct si476x_core *core, | 
|  | 144 | u16 property) | 
|  | 145 | { | 
|  | 146 | static const valid_property_pred_t is_valid_property[] = { | 
|  | 147 | [SI476X_REVISION_A10] = si476x_core_is_valid_property_a10, | 
|  | 148 | [SI476X_REVISION_A20] = si476x_core_is_valid_property_a20, | 
|  | 149 | [SI476X_REVISION_A30] = si476x_core_is_valid_property_a30, | 
|  | 150 | }; | 
|  | 151 |  | 
|  | 152 | BUG_ON(core->revision > SI476X_REVISION_A30 || | 
|  | 153 | core->revision == -1); | 
|  | 154 | return is_valid_property[core->revision](core, property); | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 |  | 
|  | 158 | static bool si476x_core_is_readonly_property(struct si476x_core *core, | 
|  | 159 | u16 property) | 
|  | 160 | { | 
|  | 161 | BUG_ON(core->revision > SI476X_REVISION_A30 || | 
|  | 162 | core->revision == -1); | 
|  | 163 |  | 
|  | 164 | switch (core->revision) { | 
|  | 165 | case SI476X_REVISION_A10: | 
|  | 166 | return (property == 0x3200); | 
|  | 167 | case SI476X_REVISION_A20: | 
|  | 168 | return (property == 0x1006 || | 
|  | 169 | property == 0x2210 || | 
|  | 170 | property == 0x3200); | 
|  | 171 | case SI476X_REVISION_A30: | 
|  | 172 | return false; | 
|  | 173 | } | 
|  | 174 |  | 
|  | 175 | return false; | 
|  | 176 | } | 
|  | 177 |  | 
|  | 178 | static bool si476x_core_regmap_readable_register(struct device *dev, | 
|  | 179 | unsigned int reg) | 
|  | 180 | { | 
|  | 181 | struct i2c_client *client = to_i2c_client(dev); | 
|  | 182 | struct si476x_core *core = i2c_get_clientdata(client); | 
|  | 183 |  | 
|  | 184 | return si476x_core_is_valid_property(core, (u16) reg); | 
|  | 185 |  | 
|  | 186 | } | 
|  | 187 |  | 
|  | 188 | static bool si476x_core_regmap_writable_register(struct device *dev, | 
|  | 189 | unsigned int reg) | 
|  | 190 | { | 
|  | 191 | struct i2c_client *client = to_i2c_client(dev); | 
|  | 192 | struct si476x_core *core = i2c_get_clientdata(client); | 
|  | 193 |  | 
|  | 194 | return si476x_core_is_valid_property(core, (u16) reg) && | 
|  | 195 | !si476x_core_is_readonly_property(core, (u16) reg); | 
|  | 196 | } | 
|  | 197 |  | 
|  | 198 |  | 
|  | 199 | static int si476x_core_regmap_write(void *context, unsigned int reg, | 
|  | 200 | unsigned int val) | 
|  | 201 | { | 
|  | 202 | return si476x_core_cmd_set_property(context, reg, val); | 
|  | 203 | } | 
|  | 204 |  | 
|  | 205 | static int si476x_core_regmap_read(void *context, unsigned int reg, | 
|  | 206 | unsigned *val) | 
|  | 207 | { | 
|  | 208 | struct si476x_core *core = context; | 
|  | 209 | int err; | 
|  | 210 |  | 
|  | 211 | err = si476x_core_cmd_get_property(core, reg); | 
|  | 212 | if (err < 0) | 
|  | 213 | return err; | 
|  | 214 |  | 
|  | 215 | *val = err; | 
|  | 216 |  | 
|  | 217 | return 0; | 
|  | 218 | } | 
|  | 219 |  | 
|  | 220 |  | 
|  | 221 | static const struct regmap_config si476x_regmap_config = { | 
|  | 222 | .reg_bits = 16, | 
|  | 223 | .val_bits = 16, | 
|  | 224 |  | 
|  | 225 | .max_register = 0x4003, | 
|  | 226 |  | 
|  | 227 | .writeable_reg = si476x_core_regmap_writable_register, | 
|  | 228 | .readable_reg = si476x_core_regmap_readable_register, | 
|  | 229 |  | 
|  | 230 | .reg_read = si476x_core_regmap_read, | 
|  | 231 | .reg_write = si476x_core_regmap_write, | 
|  | 232 |  | 
|  | 233 | .cache_type = REGCACHE_RBTREE, | 
|  | 234 | }; | 
|  | 235 |  | 
|  | 236 | struct regmap *devm_regmap_init_si476x(struct si476x_core *core) | 
|  | 237 | { | 
|  | 238 | return devm_regmap_init(&core->client->dev, NULL, | 
|  | 239 | core, &si476x_regmap_config); | 
|  | 240 | } | 
|  | 241 | EXPORT_SYMBOL_GPL(devm_regmap_init_si476x); |