Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

power: supply: Add bd718(15/28/78) charger driver

Add charger driver for ROHM BD718(15/28/78) PMIC charger block.
It is a stripped down version of the driver here:
https://lore.kernel.org/lkml/dbd97c1b0d715aa35a8b4d79741e433d97c562aa.1637061794.git.matti.vaittinen@fi.rohmeurope.com/

For the ease of review and to do a step-by-step approach remove all the
coloumb counter related stuff and do not sneak in BD71827 support. That
also avoids non-trivial rebasing of the above series.

Changes besides that:
Replace the custom property by a standard one and do not use megaohms
for the current sense resistor.

Signed-off-by: Andreas Kemnade <andreas@kemnade.info>
Reviewed-by: Matti Vaittinen <mazziesaccount@gmail.com>
Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Link: https://patch.msgid.link/20250918-bd71828-charger-v5-2-851164839c28@kemnade.info
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>

authored by

Andreas Kemnade and committed by
Sebastian Reichel
5bff79da 3a866087

+1059
+9
drivers/power/supply/Kconfig
··· 996 996 Say Y to enable support for Microchip UCS1002 Programmable 997 997 USB Port Power Controller with Charger Emulation. 998 998 999 + config CHARGER_BD71828 1000 + tristate "Power-supply driver for ROHM BD71828 and BD71815 PMIC" 1001 + depends on MFD_ROHM_BD71828 1002 + help 1003 + Say Y here to enable support for charger and battery 1004 + in ROHM BD71815, BD71817, ROHM BD71828 power management 1005 + ICs. This driver gets various bits of information about battery 1006 + and charger states. 1007 + 999 1008 config CHARGER_BD99954 1000 1009 tristate "ROHM bd99954 charger driver" 1001 1010 depends on I2C
+1
drivers/power/supply/Makefile
··· 116 116 obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o 117 117 obj-$(CONFIG_FUEL_GAUGE_STC3117) += stc3117_fuel_gauge.o 118 118 obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o 119 + obj-$(CONFIG_CHARGER_BD71828) += bd71828-power.o 119 120 obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o 120 121 obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o 121 122 obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
+1049
drivers/power/supply/bd71828-power.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-or-later 2 + /* ROHM BD71815, BD71828 and BD71878 Charger driver */ 3 + 4 + #include <linux/interrupt.h> 5 + #include <linux/kernel.h> 6 + #include <linux/mfd/rohm-bd71815.h> 7 + #include <linux/mfd/rohm-bd71828.h> 8 + #include <linux/module.h> 9 + #include <linux/mod_devicetable.h> 10 + #include <linux/platform_device.h> 11 + #include <linux/property.h> 12 + #include <linux/power_supply.h> 13 + #include <linux/slab.h> 14 + 15 + /* common defines */ 16 + #define BD7182x_MASK_VBAT_U 0x1f 17 + #define BD7182x_MASK_VDCIN_U 0x0f 18 + #define BD7182x_MASK_IBAT_U 0x3f 19 + #define BD7182x_MASK_CURDIR_DISCHG 0x80 20 + #define BD7182x_MASK_CHG_STATE 0x7f 21 + #define BD7182x_MASK_BAT_TEMP 0x07 22 + #define BD7182x_MASK_DCIN_DET BIT(0) 23 + #define BD7182x_MASK_CONF_PON BIT(0) 24 + #define BD71815_MASK_CONF_XSTB BIT(1) 25 + #define BD7182x_MASK_BAT_STAT 0x3f 26 + #define BD7182x_MASK_DCIN_STAT 0x07 27 + 28 + #define BD7182x_MASK_WDT_AUTO 0x40 29 + #define BD7182x_MASK_VBAT_ALM_LIMIT_U 0x01 30 + #define BD7182x_MASK_CHG_EN 0x01 31 + 32 + #define BD7182x_DCIN_COLLAPSE_DEFAULT 0x36 33 + 34 + #define MAX_CURRENT_DEFAULT 890000 /* uA */ 35 + #define AC_NAME "bd71828_ac" 36 + #define BAT_NAME "bd71828_bat" 37 + 38 + #define BAT_OPEN 0x7 39 + 40 + /* 41 + * VBAT Low voltage detection Threshold 42 + * 0x00D4*16mV = 212*0.016 = 3.392v 43 + */ 44 + #define VBAT_LOW_TH 0x00D4 45 + 46 + struct pwr_regs { 47 + u8 vbat_avg; 48 + u8 ibat; 49 + u8 ibat_avg; 50 + u8 btemp_vth; 51 + u8 chg_state; 52 + u8 bat_temp; 53 + u8 dcin_stat; 54 + u8 dcin_collapse_limit; 55 + u8 chg_set1; 56 + u8 chg_en; 57 + u8 vbat_alm_limit_u; 58 + u8 conf; 59 + u8 vdcin; 60 + }; 61 + 62 + static const struct pwr_regs pwr_regs_bd71828 = { 63 + .vbat_avg = BD71828_REG_VBAT_U, 64 + .ibat = BD71828_REG_IBAT_U, 65 + .ibat_avg = BD71828_REG_IBAT_AVG_U, 66 + .btemp_vth = BD71828_REG_VM_BTMP_U, 67 + .chg_state = BD71828_REG_CHG_STATE, 68 + .bat_temp = BD71828_REG_BAT_TEMP, 69 + .dcin_stat = BD71828_REG_DCIN_STAT, 70 + .dcin_collapse_limit = BD71828_REG_DCIN_CLPS, 71 + .chg_set1 = BD71828_REG_CHG_SET1, 72 + .chg_en = BD71828_REG_CHG_EN, 73 + .vbat_alm_limit_u = BD71828_REG_ALM_VBAT_LIMIT_U, 74 + .conf = BD71828_REG_CONF, 75 + .vdcin = BD71828_REG_VDCIN_U, 76 + }; 77 + 78 + static const struct pwr_regs pwr_regs_bd71815 = { 79 + .vbat_avg = BD71815_REG_VM_SA_VBAT_U, 80 + /* BD71815 does not have separate current and current avg */ 81 + .ibat = BD71815_REG_CC_CURCD_U, 82 + .ibat_avg = BD71815_REG_CC_CURCD_U, 83 + 84 + .btemp_vth = BD71815_REG_VM_BTMP, 85 + .chg_state = BD71815_REG_CHG_STATE, 86 + .bat_temp = BD71815_REG_BAT_TEMP, 87 + .dcin_stat = BD71815_REG_DCIN_STAT, 88 + .dcin_collapse_limit = BD71815_REG_DCIN_CLPS, 89 + .chg_set1 = BD71815_REG_CHG_SET1, 90 + .chg_en = BD71815_REG_CHG_SET1, 91 + .vbat_alm_limit_u = BD71815_REG_ALM_VBAT_TH_U, 92 + .conf = BD71815_REG_CONF, 93 + 94 + .vdcin = BD71815_REG_VM_DCIN_U, 95 + }; 96 + 97 + struct bd71828_power { 98 + struct regmap *regmap; 99 + enum rohm_chip_type chip_type; 100 + struct device *dev; 101 + struct power_supply *ac; 102 + struct power_supply *bat; 103 + 104 + const struct pwr_regs *regs; 105 + /* Reg val to uA */ 106 + int curr_factor; 107 + int rsens; 108 + int (*get_temp)(struct bd71828_power *pwr, int *temp); 109 + int (*bat_inserted)(struct bd71828_power *pwr); 110 + }; 111 + 112 + static int bd7182x_write16(struct bd71828_power *pwr, int reg, u16 val) 113 + { 114 + __be16 tmp; 115 + 116 + tmp = cpu_to_be16(val); 117 + 118 + return regmap_bulk_write(pwr->regmap, reg, &tmp, sizeof(tmp)); 119 + } 120 + 121 + static int bd7182x_read16_himask(struct bd71828_power *pwr, int reg, int himask, 122 + u16 *val) 123 + { 124 + struct regmap *regmap = pwr->regmap; 125 + int ret; 126 + __be16 rvals; 127 + u8 *tmp = (u8 *)&rvals; 128 + 129 + ret = regmap_bulk_read(regmap, reg, &rvals, sizeof(*val)); 130 + if (!ret) { 131 + *tmp &= himask; 132 + *val = be16_to_cpu(rvals); 133 + } 134 + 135 + return ret; 136 + } 137 + 138 + static int bd71828_get_vbat(struct bd71828_power *pwr, int *vcell) 139 + { 140 + u16 tmp_vcell; 141 + int ret; 142 + 143 + ret = bd7182x_read16_himask(pwr, pwr->regs->vbat_avg, 144 + BD7182x_MASK_VBAT_U, &tmp_vcell); 145 + if (ret) 146 + dev_err(pwr->dev, "Failed to read battery average voltage\n"); 147 + else 148 + *vcell = ((int)tmp_vcell) * 1000; 149 + 150 + return ret; 151 + } 152 + 153 + static int bd71828_get_current_ds_adc(struct bd71828_power *pwr, int *curr, int *curr_avg) 154 + { 155 + __be16 tmp_curr; 156 + char *tmp = (char *)&tmp_curr; 157 + int dir = 1; 158 + int regs[] = { pwr->regs->ibat, pwr->regs->ibat_avg }; 159 + int *vals[] = { curr, curr_avg }; 160 + int ret, i; 161 + 162 + for (dir = 1, i = 0; i < ARRAY_SIZE(regs); i++) { 163 + ret = regmap_bulk_read(pwr->regmap, regs[i], &tmp_curr, 164 + sizeof(tmp_curr)); 165 + if (ret) 166 + break; 167 + 168 + if (*tmp & BD7182x_MASK_CURDIR_DISCHG) 169 + dir = -1; 170 + 171 + *tmp &= BD7182x_MASK_IBAT_U; 172 + 173 + *vals[i] = dir * ((int)be16_to_cpu(tmp_curr)) * pwr->curr_factor; 174 + } 175 + 176 + return ret; 177 + } 178 + 179 + /* Unit is tenths of degree C */ 180 + static int bd71815_get_temp(struct bd71828_power *pwr, int *temp) 181 + { 182 + struct regmap *regmap = pwr->regmap; 183 + int ret; 184 + int t; 185 + 186 + ret = regmap_read(regmap, pwr->regs->btemp_vth, &t); 187 + if (ret) 188 + return ret; 189 + 190 + t = 200 - t; 191 + 192 + if (t > 200) { 193 + dev_err(pwr->dev, "Failed to read battery temperature\n"); 194 + return -ENODATA; 195 + } 196 + 197 + return 0; 198 + } 199 + 200 + /* Unit is tenths of degree C */ 201 + static int bd71828_get_temp(struct bd71828_power *pwr, int *temp) 202 + { 203 + u16 t; 204 + int ret; 205 + int tmp = 200 * 10000; 206 + 207 + ret = bd7182x_read16_himask(pwr, pwr->regs->btemp_vth, 208 + BD71828_MASK_VM_BTMP_U, &t); 209 + if (ret) 210 + return ret; 211 + 212 + if (t > 3200) { 213 + dev_err(pwr->dev, 214 + "Failed to read battery temperature\n"); 215 + return -ENODATA; 216 + } 217 + 218 + tmp -= 625ULL * (unsigned int)t; 219 + *temp = tmp / 1000; 220 + 221 + return ret; 222 + } 223 + 224 + static int bd71828_charge_status(struct bd71828_power *pwr, 225 + int *s, int *h) 226 + { 227 + unsigned int state; 228 + int status, health; 229 + int ret = 1; 230 + 231 + ret = regmap_read(pwr->regmap, pwr->regs->chg_state, &state); 232 + if (ret) { 233 + dev_err(pwr->dev, "charger status reading failed (%d)\n", ret); 234 + return ret; 235 + } 236 + 237 + state &= BD7182x_MASK_CHG_STATE; 238 + 239 + dev_dbg(pwr->dev, "CHG_STATE %d\n", state); 240 + 241 + switch (state) { 242 + case 0x00: 243 + status = POWER_SUPPLY_STATUS_DISCHARGING; 244 + health = POWER_SUPPLY_HEALTH_GOOD; 245 + break; 246 + case 0x01: 247 + case 0x02: 248 + case 0x03: 249 + case 0x0E: 250 + status = POWER_SUPPLY_STATUS_CHARGING; 251 + health = POWER_SUPPLY_HEALTH_GOOD; 252 + break; 253 + case 0x0F: 254 + status = POWER_SUPPLY_STATUS_FULL; 255 + health = POWER_SUPPLY_HEALTH_GOOD; 256 + break; 257 + case 0x10: 258 + case 0x11: 259 + case 0x12: 260 + case 0x13: 261 + case 0x14: 262 + case 0x20: 263 + case 0x21: 264 + case 0x22: 265 + case 0x23: 266 + case 0x24: 267 + status = POWER_SUPPLY_STATUS_NOT_CHARGING; 268 + health = POWER_SUPPLY_HEALTH_OVERHEAT; 269 + break; 270 + case 0x30: 271 + case 0x31: 272 + case 0x32: 273 + case 0x40: 274 + status = POWER_SUPPLY_STATUS_DISCHARGING; 275 + health = POWER_SUPPLY_HEALTH_GOOD; 276 + break; 277 + case 0x7f: 278 + default: 279 + status = POWER_SUPPLY_STATUS_NOT_CHARGING; 280 + health = POWER_SUPPLY_HEALTH_DEAD; 281 + break; 282 + } 283 + 284 + if (s) 285 + *s = status; 286 + if (h) 287 + *h = health; 288 + 289 + return ret; 290 + } 291 + 292 + static int get_chg_online(struct bd71828_power *pwr, int *chg_online) 293 + { 294 + int r, ret; 295 + 296 + ret = regmap_read(pwr->regmap, pwr->regs->dcin_stat, &r); 297 + if (ret) { 298 + dev_err(pwr->dev, "Failed to read DCIN status\n"); 299 + return ret; 300 + } 301 + *chg_online = ((r & BD7182x_MASK_DCIN_DET) != 0); 302 + 303 + return 0; 304 + } 305 + 306 + static int get_bat_online(struct bd71828_power *pwr, int *bat_online) 307 + { 308 + int r, ret; 309 + 310 + ret = regmap_read(pwr->regmap, pwr->regs->bat_temp, &r); 311 + if (ret) { 312 + dev_err(pwr->dev, "Failed to read battery temperature\n"); 313 + return ret; 314 + } 315 + *bat_online = ((r & BD7182x_MASK_BAT_TEMP) != BAT_OPEN); 316 + 317 + return 0; 318 + } 319 + 320 + static int bd71828_bat_inserted(struct bd71828_power *pwr) 321 + { 322 + int ret, val; 323 + 324 + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); 325 + if (ret) { 326 + dev_err(pwr->dev, "Failed to read CONF register\n"); 327 + return 0; 328 + } 329 + ret = val & BD7182x_MASK_CONF_PON; 330 + 331 + if (ret) 332 + regmap_update_bits(pwr->regmap, pwr->regs->conf, 333 + BD7182x_MASK_CONF_PON, 0); 334 + 335 + return ret; 336 + } 337 + 338 + static int bd71815_bat_inserted(struct bd71828_power *pwr) 339 + { 340 + int ret, val; 341 + 342 + ret = regmap_read(pwr->regmap, pwr->regs->conf, &val); 343 + if (ret) { 344 + dev_err(pwr->dev, "Failed to read CONF register\n"); 345 + return ret; 346 + } 347 + 348 + ret = !(val & BD71815_MASK_CONF_XSTB); 349 + if (ret) 350 + regmap_write(pwr->regmap, pwr->regs->conf, val | 351 + BD71815_MASK_CONF_XSTB); 352 + 353 + return ret; 354 + } 355 + 356 + static int bd71828_init_hardware(struct bd71828_power *pwr) 357 + { 358 + int ret; 359 + 360 + /* TODO: Collapse limit should come from device-tree ? */ 361 + ret = regmap_write(pwr->regmap, pwr->regs->dcin_collapse_limit, 362 + BD7182x_DCIN_COLLAPSE_DEFAULT); 363 + if (ret) { 364 + dev_err(pwr->dev, "Failed to write DCIN collapse limit\n"); 365 + return ret; 366 + } 367 + 368 + ret = pwr->bat_inserted(pwr); 369 + if (ret < 0) 370 + return ret; 371 + 372 + if (ret) { 373 + /* WDT_FST auto set */ 374 + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_set1, 375 + BD7182x_MASK_WDT_AUTO, 376 + BD7182x_MASK_WDT_AUTO); 377 + if (ret) 378 + return ret; 379 + 380 + ret = bd7182x_write16(pwr, pwr->regs->vbat_alm_limit_u, 381 + VBAT_LOW_TH); 382 + if (ret) 383 + return ret; 384 + 385 + /* 386 + * On BD71815 "we mask the power-state" from relax detection. 387 + * I am unsure what the impact of the power-state would be if 388 + * we didn't - but this is what the vendor driver did - and 389 + * that driver has been used in few projects so I just assume 390 + * this is needed. 391 + */ 392 + if (pwr->chip_type == ROHM_CHIP_TYPE_BD71815) { 393 + ret = regmap_set_bits(pwr->regmap, 394 + BD71815_REG_REX_CTRL_1, 395 + REX_PMU_STATE_MASK); 396 + if (ret) 397 + return ret; 398 + } 399 + } 400 + 401 + return 0; 402 + } 403 + 404 + static int bd71828_charger_get_property(struct power_supply *psy, 405 + enum power_supply_property psp, 406 + union power_supply_propval *val) 407 + { 408 + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); 409 + u32 vot; 410 + u16 tmp; 411 + int online; 412 + int ret; 413 + 414 + switch (psp) { 415 + case POWER_SUPPLY_PROP_ONLINE: 416 + ret = get_chg_online(pwr, &online); 417 + if (!ret) 418 + val->intval = online; 419 + break; 420 + case POWER_SUPPLY_PROP_VOLTAGE_NOW: 421 + ret = bd7182x_read16_himask(pwr, pwr->regs->vdcin, 422 + BD7182x_MASK_VDCIN_U, &tmp); 423 + if (ret) 424 + return ret; 425 + 426 + vot = tmp; 427 + /* 5 milli volt steps */ 428 + val->intval = 5000 * vot; 429 + break; 430 + default: 431 + return -EINVAL; 432 + } 433 + 434 + return 0; 435 + } 436 + 437 + static int bd71828_battery_get_property(struct power_supply *psy, 438 + enum power_supply_property psp, 439 + union power_supply_propval *val) 440 + { 441 + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); 442 + int ret = 0; 443 + int status, health, tmp, curr, curr_avg, chg_en; 444 + 445 + if (psp == POWER_SUPPLY_PROP_STATUS || 446 + psp == POWER_SUPPLY_PROP_HEALTH || 447 + psp == POWER_SUPPLY_PROP_CHARGE_TYPE) 448 + ret = bd71828_charge_status(pwr, &status, &health); 449 + else if (psp == POWER_SUPPLY_PROP_CURRENT_AVG || 450 + psp == POWER_SUPPLY_PROP_CURRENT_NOW) 451 + ret = bd71828_get_current_ds_adc(pwr, &curr, &curr_avg); 452 + if (ret) 453 + return ret; 454 + 455 + switch (psp) { 456 + case POWER_SUPPLY_PROP_STATUS: 457 + val->intval = status; 458 + break; 459 + case POWER_SUPPLY_PROP_HEALTH: 460 + val->intval = health; 461 + break; 462 + case POWER_SUPPLY_PROP_PRESENT: 463 + ret = get_bat_online(pwr, &tmp); 464 + if (!ret) 465 + val->intval = tmp; 466 + break; 467 + case POWER_SUPPLY_PROP_VOLTAGE_NOW: 468 + ret = bd71828_get_vbat(pwr, &tmp); 469 + val->intval = tmp; 470 + break; 471 + case POWER_SUPPLY_PROP_TECHNOLOGY: 472 + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; 473 + break; 474 + case POWER_SUPPLY_PROP_CURRENT_AVG: 475 + val->intval = curr_avg; 476 + break; 477 + case POWER_SUPPLY_PROP_CURRENT_NOW: 478 + val->intval = curr; 479 + break; 480 + case POWER_SUPPLY_PROP_CURRENT_MAX: 481 + val->intval = MAX_CURRENT_DEFAULT; 482 + break; 483 + case POWER_SUPPLY_PROP_TEMP: 484 + ret = pwr->get_temp(pwr, &val->intval); 485 + break; 486 + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: 487 + ret = regmap_read(pwr->regmap, pwr->regs->chg_en, &chg_en); 488 + if (ret) 489 + return ret; 490 + 491 + val->intval = (chg_en & BD7182x_MASK_CHG_EN) ? 492 + POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO : 493 + POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE; 494 + break; 495 + default: 496 + ret = -EINVAL; 497 + break; 498 + } 499 + 500 + return ret; 501 + } 502 + 503 + static int bd71828_battery_set_property(struct power_supply *psy, 504 + enum power_supply_property psp, 505 + const union power_supply_propval *val) 506 + { 507 + struct bd71828_power *pwr = dev_get_drvdata(psy->dev.parent); 508 + int ret = 0; 509 + 510 + switch (psp) { 511 + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: 512 + if (val->intval == POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) 513 + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, 514 + BD7182x_MASK_CHG_EN, 515 + BD7182x_MASK_CHG_EN); 516 + else 517 + ret = regmap_update_bits(pwr->regmap, pwr->regs->chg_en, 518 + BD7182x_MASK_CHG_EN, 519 + 0); 520 + break; 521 + default: 522 + return -EINVAL; 523 + } 524 + 525 + return ret; 526 + } 527 + 528 + static int bd71828_battery_property_is_writeable(struct power_supply *psy, 529 + enum power_supply_property psp) 530 + { 531 + switch (psp) { 532 + case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR: 533 + return true; 534 + default: 535 + return false; 536 + } 537 + } 538 + 539 + /** @brief ac properties */ 540 + static const enum power_supply_property bd71828_charger_props[] = { 541 + POWER_SUPPLY_PROP_ONLINE, 542 + POWER_SUPPLY_PROP_VOLTAGE_NOW, 543 + }; 544 + 545 + static const enum power_supply_property bd71828_battery_props[] = { 546 + POWER_SUPPLY_PROP_STATUS, 547 + POWER_SUPPLY_PROP_HEALTH, 548 + POWER_SUPPLY_PROP_VOLTAGE_NOW, 549 + POWER_SUPPLY_PROP_HEALTH, 550 + POWER_SUPPLY_PROP_PRESENT, 551 + POWER_SUPPLY_PROP_TECHNOLOGY, 552 + POWER_SUPPLY_PROP_TEMP, 553 + POWER_SUPPLY_PROP_CURRENT_AVG, 554 + POWER_SUPPLY_PROP_CURRENT_NOW, 555 + POWER_SUPPLY_PROP_CURRENT_MAX, 556 + POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR, 557 + }; 558 + 559 + /** @brief powers supplied by bd71828_ac */ 560 + static char *bd71828_ac_supplied_to[] = { 561 + BAT_NAME, 562 + }; 563 + 564 + static const struct power_supply_desc bd71828_ac_desc = { 565 + .name = AC_NAME, 566 + .type = POWER_SUPPLY_TYPE_MAINS, 567 + .properties = bd71828_charger_props, 568 + .num_properties = ARRAY_SIZE(bd71828_charger_props), 569 + .get_property = bd71828_charger_get_property, 570 + }; 571 + 572 + static const struct power_supply_desc bd71828_bat_desc = { 573 + .name = BAT_NAME, 574 + .type = POWER_SUPPLY_TYPE_BATTERY, 575 + .charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO) | 576 + BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE), 577 + .properties = bd71828_battery_props, 578 + .num_properties = ARRAY_SIZE(bd71828_battery_props), 579 + .get_property = bd71828_battery_get_property, 580 + .set_property = bd71828_battery_set_property, 581 + .property_is_writeable = bd71828_battery_property_is_writeable, 582 + }; 583 + 584 + #define RSENS_CURR 10000000LLU 585 + 586 + #define BD_ISR_NAME(name) \ 587 + bd7181x_##name##_isr 588 + 589 + #define BD_ISR_BAT(name, print, run_gauge) \ 590 + static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ 591 + { \ 592 + struct bd71828_power *pwr = (struct bd71828_power *)data; \ 593 + \ 594 + dev_dbg(pwr->dev, "%s\n", print); \ 595 + power_supply_changed(pwr->bat); \ 596 + \ 597 + return IRQ_HANDLED; \ 598 + } 599 + 600 + #define BD_ISR_AC(name, print, run_gauge) \ 601 + static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ 602 + { \ 603 + struct bd71828_power *pwr = (struct bd71828_power *)data; \ 604 + \ 605 + power_supply_changed(pwr->ac); \ 606 + dev_dbg(pwr->dev, "%s\n", print); \ 607 + power_supply_changed(pwr->bat); \ 608 + \ 609 + return IRQ_HANDLED; \ 610 + } 611 + 612 + #define BD_ISR_DUMMY(name, print) \ 613 + static irqreturn_t BD_ISR_NAME(name)(int irq, void *data) \ 614 + { \ 615 + struct bd71828_power *pwr = (struct bd71828_power *)data; \ 616 + \ 617 + dev_dbg(pwr->dev, "%s\n", print); \ 618 + \ 619 + return IRQ_HANDLED; \ 620 + } 621 + 622 + BD_ISR_BAT(chg_state_changed, "CHG state changed", true) 623 + /* DCIN voltage changes */ 624 + BD_ISR_AC(dcin_removed, "DCIN removed", true) 625 + BD_ISR_AC(clps_out, "DCIN voltage back to normal", true) 626 + BD_ISR_AC(clps_in, "DCIN voltage collapsed", false) 627 + BD_ISR_AC(dcin_ovp_res, "DCIN voltage normal", true) 628 + BD_ISR_AC(dcin_ovp_det, "DCIN OVER VOLTAGE", true) 629 + 630 + BD_ISR_DUMMY(dcin_mon_det, "DCIN voltage below threshold") 631 + BD_ISR_DUMMY(dcin_mon_res, "DCIN voltage above threshold") 632 + 633 + BD_ISR_DUMMY(vsys_uv_res, "VSYS under-voltage cleared") 634 + BD_ISR_DUMMY(vsys_uv_det, "VSYS under-voltage") 635 + BD_ISR_DUMMY(vsys_low_res, "'VSYS low' cleared") 636 + BD_ISR_DUMMY(vsys_low_det, "VSYS low") 637 + BD_ISR_DUMMY(vsys_mon_res, "VSYS mon - resumed") 638 + BD_ISR_DUMMY(vsys_mon_det, "VSYS mon - detected") 639 + BD_ISR_BAT(chg_wdg_temp, "charger temperature watchdog triggered", true) 640 + BD_ISR_BAT(chg_wdg, "charging watchdog triggered", true) 641 + BD_ISR_BAT(bat_removed, "Battery removed", true) 642 + BD_ISR_BAT(bat_det, "Battery detected", true) 643 + /* TODO: Verify the meaning of these interrupts */ 644 + BD_ISR_BAT(rechg_det, "Recharging", true) 645 + BD_ISR_BAT(rechg_res, "Recharge ending", true) 646 + BD_ISR_DUMMY(temp_transit, "Temperature transition") 647 + BD_ISR_BAT(therm_rmv, "bd71815-therm-rmv", false) 648 + BD_ISR_BAT(therm_det, "bd71815-therm-det", true) 649 + BD_ISR_BAT(bat_dead, "bd71815-bat-dead", false) 650 + BD_ISR_BAT(bat_short_res, "bd71815-bat-short-res", true) 651 + BD_ISR_BAT(bat_short, "bd71815-bat-short-det", false) 652 + BD_ISR_BAT(bat_low_res, "bd71815-bat-low-res", true) 653 + BD_ISR_BAT(bat_low, "bd71815-bat-low-det", true) 654 + BD_ISR_BAT(bat_ov_res, "bd71815-bat-over-res", true) 655 + /* What should we do here? */ 656 + BD_ISR_BAT(bat_ov, "bd71815-bat-over-det", false) 657 + BD_ISR_BAT(bat_mon_res, "bd71815-bat-mon-res", true) 658 + BD_ISR_BAT(bat_mon, "bd71815-bat-mon-det", true) 659 + BD_ISR_BAT(bat_cc_mon, "bd71815-bat-cc-mon2", false) 660 + BD_ISR_BAT(bat_oc1_res, "bd71815-bat-oc1-res", true) 661 + BD_ISR_BAT(bat_oc1, "bd71815-bat-oc1-det", false) 662 + BD_ISR_BAT(bat_oc2_res, "bd71815-bat-oc2-res", true) 663 + BD_ISR_BAT(bat_oc2, "bd71815-bat-oc2-det", false) 664 + BD_ISR_BAT(bat_oc3_res, "bd71815-bat-oc3-res", true) 665 + BD_ISR_BAT(bat_oc3, "bd71815-bat-oc3-det", false) 666 + BD_ISR_BAT(temp_bat_low_res, "bd71815-temp-bat-low-res", true) 667 + BD_ISR_BAT(temp_bat_low, "bd71815-temp-bat-low-det", true) 668 + BD_ISR_BAT(temp_bat_hi_res, "bd71815-temp-bat-hi-res", true) 669 + BD_ISR_BAT(temp_bat_hi, "bd71815-temp-bat-hi-det", true) 670 + 671 + static irqreturn_t bd7182x_dcin_removed(int irq, void *data) 672 + { 673 + struct bd71828_power *pwr = (struct bd71828_power *)data; 674 + 675 + power_supply_changed(pwr->ac); 676 + dev_dbg(pwr->dev, "DCIN removed\n"); 677 + 678 + return IRQ_HANDLED; 679 + } 680 + 681 + static irqreturn_t bd718x7_chg_done(int irq, void *data) 682 + { 683 + struct bd71828_power *pwr = (struct bd71828_power *)data; 684 + 685 + power_supply_changed(pwr->bat); 686 + 687 + return IRQ_HANDLED; 688 + } 689 + 690 + static irqreturn_t bd7182x_dcin_detected(int irq, void *data) 691 + { 692 + struct bd71828_power *pwr = (struct bd71828_power *)data; 693 + 694 + dev_dbg(pwr->dev, "DCIN inserted\n"); 695 + power_supply_changed(pwr->ac); 696 + 697 + return IRQ_HANDLED; 698 + } 699 + 700 + static irqreturn_t bd71828_vbat_low_res(int irq, void *data) 701 + { 702 + struct bd71828_power *pwr = (struct bd71828_power *)data; 703 + 704 + dev_dbg(pwr->dev, "VBAT LOW Resumed\n"); 705 + 706 + return IRQ_HANDLED; 707 + } 708 + 709 + static irqreturn_t bd71828_vbat_low_det(int irq, void *data) 710 + { 711 + struct bd71828_power *pwr = (struct bd71828_power *)data; 712 + 713 + dev_dbg(pwr->dev, "VBAT LOW Detected\n"); 714 + 715 + return IRQ_HANDLED; 716 + } 717 + 718 + static irqreturn_t bd71828_temp_bat_hi_det(int irq, void *data) 719 + { 720 + struct bd71828_power *pwr = (struct bd71828_power *)data; 721 + 722 + dev_warn(pwr->dev, "Overtemp Detected\n"); 723 + power_supply_changed(pwr->bat); 724 + 725 + return IRQ_HANDLED; 726 + } 727 + 728 + static irqreturn_t bd71828_temp_bat_hi_res(int irq, void *data) 729 + { 730 + struct bd71828_power *pwr = (struct bd71828_power *)data; 731 + 732 + dev_dbg(pwr->dev, "Overtemp Resumed\n"); 733 + power_supply_changed(pwr->bat); 734 + 735 + return IRQ_HANDLED; 736 + } 737 + 738 + static irqreturn_t bd71828_temp_bat_low_det(int irq, void *data) 739 + { 740 + struct bd71828_power *pwr = (struct bd71828_power *)data; 741 + 742 + dev_dbg(pwr->dev, "Lowtemp Detected\n"); 743 + power_supply_changed(pwr->bat); 744 + 745 + return IRQ_HANDLED; 746 + } 747 + 748 + static irqreturn_t bd71828_temp_bat_low_res(int irq, void *data) 749 + { 750 + struct bd71828_power *pwr = (struct bd71828_power *)data; 751 + 752 + dev_dbg(pwr->dev, "Lowtemp Resumed\n"); 753 + power_supply_changed(pwr->bat); 754 + 755 + return IRQ_HANDLED; 756 + } 757 + 758 + static irqreturn_t bd71828_temp_vf_det(int irq, void *data) 759 + { 760 + struct bd71828_power *pwr = (struct bd71828_power *)data; 761 + 762 + dev_dbg(pwr->dev, "VF Detected\n"); 763 + power_supply_changed(pwr->bat); 764 + 765 + return IRQ_HANDLED; 766 + } 767 + 768 + static irqreturn_t bd71828_temp_vf_res(int irq, void *data) 769 + { 770 + struct bd71828_power *pwr = (struct bd71828_power *)data; 771 + 772 + dev_dbg(pwr->dev, "VF Resumed\n"); 773 + power_supply_changed(pwr->bat); 774 + 775 + return IRQ_HANDLED; 776 + } 777 + 778 + static irqreturn_t bd71828_temp_vf125_det(int irq, void *data) 779 + { 780 + struct bd71828_power *pwr = (struct bd71828_power *)data; 781 + 782 + dev_dbg(pwr->dev, "VF125 Detected\n"); 783 + power_supply_changed(pwr->bat); 784 + 785 + return IRQ_HANDLED; 786 + } 787 + 788 + static irqreturn_t bd71828_temp_vf125_res(int irq, void *data) 789 + { 790 + struct bd71828_power *pwr = (struct bd71828_power *)data; 791 + 792 + dev_dbg(pwr->dev, "VF125 Resumed\n"); 793 + power_supply_changed(pwr->bat); 794 + 795 + return IRQ_HANDLED; 796 + } 797 + 798 + struct bd7182x_irq_res { 799 + const char *name; 800 + irq_handler_t handler; 801 + }; 802 + 803 + #define BDIRQ(na, hn) { .name = (na), .handler = (hn) } 804 + 805 + static int bd7182x_get_irqs(struct platform_device *pdev, 806 + struct bd71828_power *pwr) 807 + { 808 + int i, irq, ret; 809 + static const struct bd7182x_irq_res bd71815_irqs[] = { 810 + BDIRQ("bd71815-dcin-rmv", BD_ISR_NAME(dcin_removed)), 811 + BDIRQ("bd71815-dcin-clps-out", BD_ISR_NAME(clps_out)), 812 + BDIRQ("bd71815-dcin-clps-in", BD_ISR_NAME(clps_in)), 813 + BDIRQ("bd71815-dcin-ovp-res", BD_ISR_NAME(dcin_ovp_res)), 814 + BDIRQ("bd71815-dcin-ovp-det", BD_ISR_NAME(dcin_ovp_det)), 815 + BDIRQ("bd71815-dcin-mon-res", BD_ISR_NAME(dcin_mon_res)), 816 + BDIRQ("bd71815-dcin-mon-det", BD_ISR_NAME(dcin_mon_det)), 817 + 818 + BDIRQ("bd71815-vsys-uv-res", BD_ISR_NAME(vsys_uv_res)), 819 + BDIRQ("bd71815-vsys-uv-det", BD_ISR_NAME(vsys_uv_det)), 820 + BDIRQ("bd71815-vsys-low-res", BD_ISR_NAME(vsys_low_res)), 821 + BDIRQ("bd71815-vsys-low-det", BD_ISR_NAME(vsys_low_det)), 822 + BDIRQ("bd71815-vsys-mon-res", BD_ISR_NAME(vsys_mon_res)), 823 + BDIRQ("bd71815-vsys-mon-det", BD_ISR_NAME(vsys_mon_det)), 824 + BDIRQ("bd71815-chg-wdg-temp", BD_ISR_NAME(chg_wdg_temp)), 825 + BDIRQ("bd71815-chg-wdg", BD_ISR_NAME(chg_wdg)), 826 + BDIRQ("bd71815-rechg-det", BD_ISR_NAME(rechg_det)), 827 + BDIRQ("bd71815-rechg-res", BD_ISR_NAME(rechg_res)), 828 + BDIRQ("bd71815-ranged-temp-transit", BD_ISR_NAME(temp_transit)), 829 + BDIRQ("bd71815-chg-state-change", BD_ISR_NAME(chg_state_changed)), 830 + BDIRQ("bd71815-bat-temp-normal", bd71828_temp_bat_hi_res), 831 + BDIRQ("bd71815-bat-temp-erange", bd71828_temp_bat_hi_det), 832 + BDIRQ("bd71815-bat-rmv", BD_ISR_NAME(bat_removed)), 833 + BDIRQ("bd71815-bat-det", BD_ISR_NAME(bat_det)), 834 + 835 + /* Add ISRs for these */ 836 + BDIRQ("bd71815-therm-rmv", BD_ISR_NAME(therm_rmv)), 837 + BDIRQ("bd71815-therm-det", BD_ISR_NAME(therm_det)), 838 + BDIRQ("bd71815-bat-dead", BD_ISR_NAME(bat_dead)), 839 + BDIRQ("bd71815-bat-short-res", BD_ISR_NAME(bat_short_res)), 840 + BDIRQ("bd71815-bat-short-det", BD_ISR_NAME(bat_short)), 841 + BDIRQ("bd71815-bat-low-res", BD_ISR_NAME(bat_low_res)), 842 + BDIRQ("bd71815-bat-low-det", BD_ISR_NAME(bat_low)), 843 + BDIRQ("bd71815-bat-over-res", BD_ISR_NAME(bat_ov_res)), 844 + BDIRQ("bd71815-bat-over-det", BD_ISR_NAME(bat_ov)), 845 + BDIRQ("bd71815-bat-mon-res", BD_ISR_NAME(bat_mon_res)), 846 + BDIRQ("bd71815-bat-mon-det", BD_ISR_NAME(bat_mon)), 847 + /* cc-mon 1 & 3 ? */ 848 + BDIRQ("bd71815-bat-cc-mon2", BD_ISR_NAME(bat_cc_mon)), 849 + BDIRQ("bd71815-bat-oc1-res", BD_ISR_NAME(bat_oc1_res)), 850 + BDIRQ("bd71815-bat-oc1-det", BD_ISR_NAME(bat_oc1)), 851 + BDIRQ("bd71815-bat-oc2-res", BD_ISR_NAME(bat_oc2_res)), 852 + BDIRQ("bd71815-bat-oc2-det", BD_ISR_NAME(bat_oc2)), 853 + BDIRQ("bd71815-bat-oc3-res", BD_ISR_NAME(bat_oc3_res)), 854 + BDIRQ("bd71815-bat-oc3-det", BD_ISR_NAME(bat_oc3)), 855 + BDIRQ("bd71815-temp-bat-low-res", BD_ISR_NAME(temp_bat_low_res)), 856 + BDIRQ("bd71815-temp-bat-low-det", BD_ISR_NAME(temp_bat_low)), 857 + BDIRQ("bd71815-temp-bat-hi-res", BD_ISR_NAME(temp_bat_hi_res)), 858 + BDIRQ("bd71815-temp-bat-hi-det", BD_ISR_NAME(temp_bat_hi)), 859 + /* 860 + * TODO: add rest of the IRQs and re-check the handling. 861 + * Check the bd71815-bat-cc-mon1, bd71815-bat-cc-mon3, 862 + * bd71815-bat-low-res, bd71815-bat-low-det, 863 + * bd71815-bat-hi-res, bd71815-bat-hi-det. 864 + */ 865 + }; 866 + static const struct bd7182x_irq_res bd71828_irqs[] = { 867 + BDIRQ("bd71828-chg-done", bd718x7_chg_done), 868 + BDIRQ("bd71828-pwr-dcin-in", bd7182x_dcin_detected), 869 + BDIRQ("bd71828-pwr-dcin-out", bd7182x_dcin_removed), 870 + BDIRQ("bd71828-vbat-normal", bd71828_vbat_low_res), 871 + BDIRQ("bd71828-vbat-low", bd71828_vbat_low_det), 872 + BDIRQ("bd71828-btemp-hi", bd71828_temp_bat_hi_det), 873 + BDIRQ("bd71828-btemp-cool", bd71828_temp_bat_hi_res), 874 + BDIRQ("bd71828-btemp-lo", bd71828_temp_bat_low_det), 875 + BDIRQ("bd71828-btemp-warm", bd71828_temp_bat_low_res), 876 + BDIRQ("bd71828-temp-hi", bd71828_temp_vf_det), 877 + BDIRQ("bd71828-temp-norm", bd71828_temp_vf_res), 878 + BDIRQ("bd71828-temp-125-over", bd71828_temp_vf125_det), 879 + BDIRQ("bd71828-temp-125-under", bd71828_temp_vf125_res), 880 + }; 881 + int num_irqs; 882 + const struct bd7182x_irq_res *irqs; 883 + 884 + switch (pwr->chip_type) { 885 + case ROHM_CHIP_TYPE_BD71828: 886 + irqs = &bd71828_irqs[0]; 887 + num_irqs = ARRAY_SIZE(bd71828_irqs); 888 + break; 889 + case ROHM_CHIP_TYPE_BD71815: 890 + irqs = &bd71815_irqs[0]; 891 + num_irqs = ARRAY_SIZE(bd71815_irqs); 892 + break; 893 + default: 894 + return -EINVAL; 895 + } 896 + 897 + for (i = 0; i < num_irqs; i++) { 898 + irq = platform_get_irq_byname(pdev, irqs[i].name); 899 + 900 + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, 901 + irqs[i].handler, 0, 902 + irqs[i].name, pwr); 903 + if (ret) 904 + break; 905 + } 906 + 907 + return ret; 908 + } 909 + 910 + #define RSENS_DEFAULT_30MOHM 30000 /* 30 mOhm in uOhms*/ 911 + 912 + static int bd7182x_get_rsens(struct bd71828_power *pwr) 913 + { 914 + u64 tmp = RSENS_CURR; 915 + int rsens_ohm = RSENS_DEFAULT_30MOHM; 916 + struct fwnode_handle *node = NULL; 917 + 918 + if (pwr->dev->parent) 919 + node = dev_fwnode(pwr->dev->parent); 920 + 921 + if (node) { 922 + int ret; 923 + u32 rs; 924 + 925 + ret = fwnode_property_read_u32(node, 926 + "rohm,charger-sense-resistor-micro-ohms", 927 + &rs); 928 + if (ret) { 929 + if (ret == -EINVAL) { 930 + rs = RSENS_DEFAULT_30MOHM; 931 + } else { 932 + dev_err(pwr->dev, "Bad RSENS dt property\n"); 933 + return ret; 934 + } 935 + } 936 + if (!rs) { 937 + dev_err(pwr->dev, "Bad RSENS value\n"); 938 + return -EINVAL; 939 + } 940 + 941 + rsens_ohm = (int)rs; 942 + } 943 + 944 + /* Reg val to uA */ 945 + do_div(tmp, rsens_ohm); 946 + 947 + pwr->curr_factor = tmp; 948 + pwr->rsens = rsens_ohm; 949 + dev_dbg(pwr->dev, "Setting rsens to %u micro ohm\n", pwr->rsens); 950 + dev_dbg(pwr->dev, "Setting curr-factor to %u\n", pwr->curr_factor); 951 + 952 + return 0; 953 + } 954 + 955 + static int bd71828_power_probe(struct platform_device *pdev) 956 + { 957 + struct bd71828_power *pwr; 958 + struct power_supply_config ac_cfg = {}; 959 + struct power_supply_config bat_cfg = {}; 960 + int ret; 961 + struct regmap *regmap; 962 + 963 + regmap = dev_get_regmap(pdev->dev.parent, NULL); 964 + if (!regmap) { 965 + dev_err(&pdev->dev, "No parent regmap\n"); 966 + return -EINVAL; 967 + } 968 + 969 + pwr = devm_kzalloc(&pdev->dev, sizeof(*pwr), GFP_KERNEL); 970 + if (!pwr) 971 + return -ENOMEM; 972 + 973 + pwr->regmap = regmap; 974 + pwr->dev = &pdev->dev; 975 + pwr->chip_type = platform_get_device_id(pdev)->driver_data; 976 + 977 + switch (pwr->chip_type) { 978 + case ROHM_CHIP_TYPE_BD71828: 979 + pwr->bat_inserted = bd71828_bat_inserted; 980 + pwr->get_temp = bd71828_get_temp; 981 + pwr->regs = &pwr_regs_bd71828; 982 + break; 983 + case ROHM_CHIP_TYPE_BD71815: 984 + pwr->bat_inserted = bd71815_bat_inserted; 985 + pwr->get_temp = bd71815_get_temp; 986 + pwr->regs = &pwr_regs_bd71815; 987 + break; 988 + default: 989 + dev_err(pwr->dev, "Unknown PMIC\n"); 990 + return -EINVAL; 991 + } 992 + 993 + ret = bd7182x_get_rsens(pwr); 994 + if (ret) 995 + return dev_err_probe(&pdev->dev, ret, "sense resistor missing\n"); 996 + 997 + dev_set_drvdata(&pdev->dev, pwr); 998 + bd71828_init_hardware(pwr); 999 + 1000 + bat_cfg.drv_data = pwr; 1001 + bat_cfg.fwnode = dev_fwnode(&pdev->dev); 1002 + 1003 + ac_cfg.supplied_to = bd71828_ac_supplied_to; 1004 + ac_cfg.num_supplicants = ARRAY_SIZE(bd71828_ac_supplied_to); 1005 + ac_cfg.drv_data = pwr; 1006 + 1007 + pwr->ac = devm_power_supply_register(&pdev->dev, &bd71828_ac_desc, 1008 + &ac_cfg); 1009 + if (IS_ERR(pwr->ac)) 1010 + return dev_err_probe(&pdev->dev, PTR_ERR(pwr->ac), 1011 + "failed to register ac\n"); 1012 + 1013 + pwr->bat = devm_power_supply_register(&pdev->dev, &bd71828_bat_desc, 1014 + &bat_cfg); 1015 + if (IS_ERR(pwr->bat)) 1016 + return dev_err_probe(&pdev->dev, PTR_ERR(pwr->bat), 1017 + "failed to register bat\n"); 1018 + 1019 + ret = bd7182x_get_irqs(pdev, pwr); 1020 + if (ret) 1021 + return dev_err_probe(&pdev->dev, ret, "failed to request IRQs"); 1022 + 1023 + /* Configure wakeup capable */ 1024 + device_set_wakeup_capable(pwr->dev, 1); 1025 + device_set_wakeup_enable(pwr->dev, 1); 1026 + 1027 + return 0; 1028 + } 1029 + 1030 + static const struct platform_device_id bd71828_charger_id[] = { 1031 + { "bd71815-power", ROHM_CHIP_TYPE_BD71815 }, 1032 + { "bd71828-power", ROHM_CHIP_TYPE_BD71828 }, 1033 + { }, 1034 + }; 1035 + MODULE_DEVICE_TABLE(platform, bd71828_charger_id); 1036 + 1037 + static struct platform_driver bd71828_power_driver = { 1038 + .driver = { 1039 + .name = "bd718xx-power", 1040 + }, 1041 + .probe = bd71828_power_probe, 1042 + .id_table = bd71828_charger_id, 1043 + }; 1044 + 1045 + module_platform_driver(bd71828_power_driver); 1046 + 1047 + MODULE_AUTHOR("Cong Pham <cpham2403@gmail.com>"); 1048 + MODULE_DESCRIPTION("ROHM BD718(15/28/78) PMIC Battery Charger driver"); 1049 + MODULE_LICENSE("GPL");