| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Battery driver for One Laptop Per Child board. |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright © 2006-2010 David Woodhouse <dwmw2@infradead.org> |
|---|
| 5 | | - * |
|---|
| 6 | | - * This program is free software; you can redistribute it and/or modify |
|---|
| 7 | | - * it under the terms of the GNU General Public License version 2 as |
|---|
| 8 | | - * published by the Free Software Foundation. |
|---|
| 9 | 6 | */ |
|---|
| 10 | 7 | |
|---|
| 11 | 8 | #include <linux/kernel.h> |
|---|
| .. | .. |
|---|
| 14 | 11 | #include <linux/types.h> |
|---|
| 15 | 12 | #include <linux/err.h> |
|---|
| 16 | 13 | #include <linux/device.h> |
|---|
| 14 | +#include <linux/of.h> |
|---|
| 17 | 15 | #include <linux/platform_device.h> |
|---|
| 18 | 16 | #include <linux/power_supply.h> |
|---|
| 19 | 17 | #include <linux/jiffies.h> |
|---|
| 20 | 18 | #include <linux/sched.h> |
|---|
| 21 | 19 | #include <linux/olpc-ec.h> |
|---|
| 22 | | -#include <asm/olpc.h> |
|---|
| 23 | 20 | |
|---|
| 24 | 21 | |
|---|
| 25 | 22 | #define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ |
|---|
| .. | .. |
|---|
| 52 | 49 | |
|---|
| 53 | 50 | #define BAT_ADDR_MFR_TYPE 0x5F |
|---|
| 54 | 51 | |
|---|
| 52 | +struct olpc_battery_data { |
|---|
| 53 | + struct power_supply *olpc_ac; |
|---|
| 54 | + struct power_supply *olpc_bat; |
|---|
| 55 | + char bat_serial[17]; |
|---|
| 56 | + bool new_proto; |
|---|
| 57 | + bool little_endian; |
|---|
| 58 | +}; |
|---|
| 59 | + |
|---|
| 55 | 60 | /********************************************************************* |
|---|
| 56 | 61 | * Power |
|---|
| 57 | 62 | *********************************************************************/ |
|---|
| .. | .. |
|---|
| 83 | 88 | }; |
|---|
| 84 | 89 | |
|---|
| 85 | 90 | static const struct power_supply_desc olpc_ac_desc = { |
|---|
| 86 | | - .name = "olpc-ac", |
|---|
| 91 | + .name = "olpc_ac", |
|---|
| 87 | 92 | .type = POWER_SUPPLY_TYPE_MAINS, |
|---|
| 88 | 93 | .properties = olpc_ac_props, |
|---|
| 89 | 94 | .num_properties = ARRAY_SIZE(olpc_ac_props), |
|---|
| 90 | 95 | .get_property = olpc_ac_get_prop, |
|---|
| 91 | 96 | }; |
|---|
| 92 | 97 | |
|---|
| 93 | | -static struct power_supply *olpc_ac; |
|---|
| 94 | | - |
|---|
| 95 | | -static char bat_serial[17]; /* Ick */ |
|---|
| 96 | | - |
|---|
| 97 | | -static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte) |
|---|
| 98 | +static int olpc_bat_get_status(struct olpc_battery_data *data, |
|---|
| 99 | + union power_supply_propval *val, uint8_t ec_byte) |
|---|
| 98 | 100 | { |
|---|
| 99 | | - if (olpc_platform_info.ecver > 0x44) { |
|---|
| 101 | + if (data->new_proto) { |
|---|
| 100 | 102 | if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE)) |
|---|
| 101 | 103 | val->intval = POWER_SUPPLY_STATUS_CHARGING; |
|---|
| 102 | 104 | else if (ec_byte & BAT_STAT_DISCHARGING) |
|---|
| .. | .. |
|---|
| 318 | 320 | return ret; |
|---|
| 319 | 321 | } |
|---|
| 320 | 322 | |
|---|
| 323 | +static u16 ecword_to_cpu(struct olpc_battery_data *data, u16 ec_word) |
|---|
| 324 | +{ |
|---|
| 325 | + if (data->little_endian) |
|---|
| 326 | + return le16_to_cpu((__force __le16)ec_word); |
|---|
| 327 | + else |
|---|
| 328 | + return be16_to_cpu((__force __be16)ec_word); |
|---|
| 329 | +} |
|---|
| 330 | + |
|---|
| 321 | 331 | /********************************************************************* |
|---|
| 322 | 332 | * Battery properties |
|---|
| 323 | 333 | *********************************************************************/ |
|---|
| .. | .. |
|---|
| 325 | 335 | enum power_supply_property psp, |
|---|
| 326 | 336 | union power_supply_propval *val) |
|---|
| 327 | 337 | { |
|---|
| 338 | + struct olpc_battery_data *data = power_supply_get_drvdata(psy); |
|---|
| 328 | 339 | int ret = 0; |
|---|
| 329 | | - __be16 ec_word; |
|---|
| 340 | + u16 ec_word; |
|---|
| 330 | 341 | uint8_t ec_byte; |
|---|
| 331 | 342 | __be64 ser_buf; |
|---|
| 332 | 343 | |
|---|
| .. | .. |
|---|
| 346 | 357 | |
|---|
| 347 | 358 | switch (psp) { |
|---|
| 348 | 359 | case POWER_SUPPLY_PROP_STATUS: |
|---|
| 349 | | - ret = olpc_bat_get_status(val, ec_byte); |
|---|
| 360 | + ret = olpc_bat_get_status(data, val, ec_byte); |
|---|
| 350 | 361 | if (ret) |
|---|
| 351 | 362 | return ret; |
|---|
| 352 | 363 | break; |
|---|
| .. | .. |
|---|
| 389 | 400 | if (ret) |
|---|
| 390 | 401 | return ret; |
|---|
| 391 | 402 | |
|---|
| 392 | | - val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32; |
|---|
| 403 | + val->intval = ecword_to_cpu(data, ec_word) * 9760L / 32; |
|---|
| 393 | 404 | break; |
|---|
| 394 | 405 | case POWER_SUPPLY_PROP_CURRENT_AVG: |
|---|
| 395 | 406 | case POWER_SUPPLY_PROP_CURRENT_NOW: |
|---|
| .. | .. |
|---|
| 397 | 408 | if (ret) |
|---|
| 398 | 409 | return ret; |
|---|
| 399 | 410 | |
|---|
| 400 | | - val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120; |
|---|
| 411 | + val->intval = ecword_to_cpu(data, ec_word) * 15625L / 120; |
|---|
| 401 | 412 | break; |
|---|
| 402 | 413 | case POWER_SUPPLY_PROP_CAPACITY: |
|---|
| 403 | 414 | ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1); |
|---|
| .. | .. |
|---|
| 428 | 439 | if (ret) |
|---|
| 429 | 440 | return ret; |
|---|
| 430 | 441 | |
|---|
| 431 | | - val->intval = (s16)be16_to_cpu(ec_word) * 10 / 256; |
|---|
| 442 | + val->intval = ecword_to_cpu(data, ec_word) * 10 / 256; |
|---|
| 432 | 443 | break; |
|---|
| 433 | 444 | case POWER_SUPPLY_PROP_TEMP_AMBIENT: |
|---|
| 434 | 445 | ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2); |
|---|
| 435 | 446 | if (ret) |
|---|
| 436 | 447 | return ret; |
|---|
| 437 | 448 | |
|---|
| 438 | | - val->intval = (int)be16_to_cpu(ec_word) * 10 / 256; |
|---|
| 449 | + val->intval = (int)ecword_to_cpu(data, ec_word) * 10 / 256; |
|---|
| 439 | 450 | break; |
|---|
| 440 | 451 | case POWER_SUPPLY_PROP_CHARGE_COUNTER: |
|---|
| 441 | 452 | ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2); |
|---|
| 442 | 453 | if (ret) |
|---|
| 443 | 454 | return ret; |
|---|
| 444 | 455 | |
|---|
| 445 | | - val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15; |
|---|
| 456 | + val->intval = ecword_to_cpu(data, ec_word) * 6250 / 15; |
|---|
| 446 | 457 | break; |
|---|
| 447 | 458 | case POWER_SUPPLY_PROP_SERIAL_NUMBER: |
|---|
| 448 | 459 | ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8); |
|---|
| 449 | 460 | if (ret) |
|---|
| 450 | 461 | return ret; |
|---|
| 451 | 462 | |
|---|
| 452 | | - sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); |
|---|
| 453 | | - val->strval = bat_serial; |
|---|
| 463 | + sprintf(data->bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); |
|---|
| 464 | + val->strval = data->bat_serial; |
|---|
| 454 | 465 | break; |
|---|
| 455 | 466 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
|---|
| 456 | 467 | ret = olpc_bat_get_voltage_max_design(val); |
|---|
| .. | .. |
|---|
| 536 | 547 | return count; |
|---|
| 537 | 548 | } |
|---|
| 538 | 549 | |
|---|
| 539 | | -static const struct bin_attribute olpc_bat_eeprom = { |
|---|
| 550 | +static struct bin_attribute olpc_bat_eeprom = { |
|---|
| 540 | 551 | .attr = { |
|---|
| 541 | 552 | .name = "eeprom", |
|---|
| 542 | 553 | .mode = S_IRUGO, |
|---|
| .. | .. |
|---|
| 560 | 571 | return sprintf(buf, "%d\n", ec_byte); |
|---|
| 561 | 572 | } |
|---|
| 562 | 573 | |
|---|
| 563 | | -static const struct device_attribute olpc_bat_error = { |
|---|
| 574 | +static struct device_attribute olpc_bat_error = { |
|---|
| 564 | 575 | .attr = { |
|---|
| 565 | 576 | .name = "error", |
|---|
| 566 | 577 | .mode = S_IRUGO, |
|---|
| .. | .. |
|---|
| 568 | 579 | .show = olpc_bat_error_read, |
|---|
| 569 | 580 | }; |
|---|
| 570 | 581 | |
|---|
| 582 | +static struct attribute *olpc_bat_sysfs_attrs[] = { |
|---|
| 583 | + &olpc_bat_error.attr, |
|---|
| 584 | + NULL |
|---|
| 585 | +}; |
|---|
| 586 | + |
|---|
| 587 | +static struct bin_attribute *olpc_bat_sysfs_bin_attrs[] = { |
|---|
| 588 | + &olpc_bat_eeprom, |
|---|
| 589 | + NULL |
|---|
| 590 | +}; |
|---|
| 591 | + |
|---|
| 592 | +static const struct attribute_group olpc_bat_sysfs_group = { |
|---|
| 593 | + .attrs = olpc_bat_sysfs_attrs, |
|---|
| 594 | + .bin_attrs = olpc_bat_sysfs_bin_attrs, |
|---|
| 595 | + |
|---|
| 596 | +}; |
|---|
| 597 | + |
|---|
| 598 | +static const struct attribute_group *olpc_bat_sysfs_groups[] = { |
|---|
| 599 | + &olpc_bat_sysfs_group, |
|---|
| 600 | + NULL |
|---|
| 601 | +}; |
|---|
| 602 | + |
|---|
| 571 | 603 | /********************************************************************* |
|---|
| 572 | 604 | * Initialisation |
|---|
| 573 | 605 | *********************************************************************/ |
|---|
| 574 | 606 | |
|---|
| 575 | 607 | static struct power_supply_desc olpc_bat_desc = { |
|---|
| 576 | | - .name = "olpc-battery", |
|---|
| 608 | + .name = "olpc_battery", |
|---|
| 577 | 609 | .get_property = olpc_bat_get_property, |
|---|
| 578 | 610 | .use_for_apm = 1, |
|---|
| 579 | 611 | }; |
|---|
| 580 | 612 | |
|---|
| 581 | | -static struct power_supply *olpc_bat; |
|---|
| 582 | | - |
|---|
| 583 | 613 | static int olpc_battery_suspend(struct platform_device *pdev, |
|---|
| 584 | 614 | pm_message_t state) |
|---|
| 585 | 615 | { |
|---|
| 586 | | - if (device_may_wakeup(&olpc_ac->dev)) |
|---|
| 616 | + struct olpc_battery_data *data = platform_get_drvdata(pdev); |
|---|
| 617 | + |
|---|
| 618 | + if (device_may_wakeup(&data->olpc_ac->dev)) |
|---|
| 587 | 619 | olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR); |
|---|
| 588 | 620 | else |
|---|
| 589 | 621 | olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR); |
|---|
| 590 | 622 | |
|---|
| 591 | | - if (device_may_wakeup(&olpc_bat->dev)) |
|---|
| 623 | + if (device_may_wakeup(&data->olpc_bat->dev)) |
|---|
| 592 | 624 | olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC |
|---|
| 593 | 625 | | EC_SCI_SRC_BATERR); |
|---|
| 594 | 626 | else |
|---|
| .. | .. |
|---|
| 600 | 632 | |
|---|
| 601 | 633 | static int olpc_battery_probe(struct platform_device *pdev) |
|---|
| 602 | 634 | { |
|---|
| 603 | | - int ret; |
|---|
| 635 | + struct power_supply_config bat_psy_cfg = {}; |
|---|
| 636 | + struct power_supply_config ac_psy_cfg = {}; |
|---|
| 637 | + struct olpc_battery_data *data; |
|---|
| 604 | 638 | uint8_t status; |
|---|
| 639 | + uint8_t ecver; |
|---|
| 640 | + int ret; |
|---|
| 605 | 641 | |
|---|
| 606 | | - /* |
|---|
| 607 | | - * We've seen a number of EC protocol changes; this driver requires |
|---|
| 608 | | - * the latest EC protocol, supported by 0x44 and above. |
|---|
| 609 | | - */ |
|---|
| 610 | | - if (olpc_platform_info.ecver < 0x44) { |
|---|
| 642 | + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); |
|---|
| 643 | + if (!data) |
|---|
| 644 | + return -ENOMEM; |
|---|
| 645 | + platform_set_drvdata(pdev, data); |
|---|
| 646 | + |
|---|
| 647 | + /* See if the EC is already there and get the EC revision */ |
|---|
| 648 | + ret = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ecver, 1); |
|---|
| 649 | + if (ret) |
|---|
| 650 | + return ret; |
|---|
| 651 | + |
|---|
| 652 | + if (of_find_compatible_node(NULL, NULL, "olpc,xo1.75-ec")) { |
|---|
| 653 | + /* XO 1.75 */ |
|---|
| 654 | + data->new_proto = true; |
|---|
| 655 | + data->little_endian = true; |
|---|
| 656 | + } else if (ecver > 0x44) { |
|---|
| 657 | + /* XO 1 or 1.5 with a new EC firmware. */ |
|---|
| 658 | + data->new_proto = true; |
|---|
| 659 | + } else if (ecver < 0x44) { |
|---|
| 660 | + /* |
|---|
| 661 | + * We've seen a number of EC protocol changes; this driver |
|---|
| 662 | + * requires the latest EC protocol, supported by 0x44 and above. |
|---|
| 663 | + */ |
|---|
| 611 | 664 | printk(KERN_NOTICE "OLPC EC version 0x%02x too old for " |
|---|
| 612 | | - "battery driver.\n", olpc_platform_info.ecver); |
|---|
| 665 | + "battery driver.\n", ecver); |
|---|
| 613 | 666 | return -ENXIO; |
|---|
| 614 | 667 | } |
|---|
| 615 | 668 | |
|---|
| .. | .. |
|---|
| 619 | 672 | |
|---|
| 620 | 673 | /* Ignore the status. It doesn't actually matter */ |
|---|
| 621 | 674 | |
|---|
| 622 | | - olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL); |
|---|
| 623 | | - if (IS_ERR(olpc_ac)) |
|---|
| 624 | | - return PTR_ERR(olpc_ac); |
|---|
| 675 | + ac_psy_cfg.of_node = pdev->dev.of_node; |
|---|
| 676 | + ac_psy_cfg.drv_data = data; |
|---|
| 625 | 677 | |
|---|
| 626 | | - if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */ |
|---|
| 678 | + data->olpc_ac = devm_power_supply_register(&pdev->dev, &olpc_ac_desc, |
|---|
| 679 | + &ac_psy_cfg); |
|---|
| 680 | + if (IS_ERR(data->olpc_ac)) |
|---|
| 681 | + return PTR_ERR(data->olpc_ac); |
|---|
| 682 | + |
|---|
| 683 | + if (of_device_is_compatible(pdev->dev.of_node, "olpc,xo1.5-battery")) { |
|---|
| 684 | + /* XO-1.5 */ |
|---|
| 627 | 685 | olpc_bat_desc.properties = olpc_xo15_bat_props; |
|---|
| 628 | 686 | olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props); |
|---|
| 629 | | - } else { /* XO-1 */ |
|---|
| 687 | + } else { |
|---|
| 688 | + /* XO-1 */ |
|---|
| 630 | 689 | olpc_bat_desc.properties = olpc_xo1_bat_props; |
|---|
| 631 | 690 | olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props); |
|---|
| 632 | 691 | } |
|---|
| 633 | 692 | |
|---|
| 634 | | - olpc_bat = power_supply_register(&pdev->dev, &olpc_bat_desc, NULL); |
|---|
| 635 | | - if (IS_ERR(olpc_bat)) { |
|---|
| 636 | | - ret = PTR_ERR(olpc_bat); |
|---|
| 637 | | - goto battery_failed; |
|---|
| 638 | | - } |
|---|
| 693 | + bat_psy_cfg.of_node = pdev->dev.of_node; |
|---|
| 694 | + bat_psy_cfg.drv_data = data; |
|---|
| 695 | + bat_psy_cfg.attr_grp = olpc_bat_sysfs_groups; |
|---|
| 639 | 696 | |
|---|
| 640 | | - ret = device_create_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); |
|---|
| 641 | | - if (ret) |
|---|
| 642 | | - goto eeprom_failed; |
|---|
| 643 | | - |
|---|
| 644 | | - ret = device_create_file(&olpc_bat->dev, &olpc_bat_error); |
|---|
| 645 | | - if (ret) |
|---|
| 646 | | - goto error_failed; |
|---|
| 697 | + data->olpc_bat = devm_power_supply_register(&pdev->dev, &olpc_bat_desc, |
|---|
| 698 | + &bat_psy_cfg); |
|---|
| 699 | + if (IS_ERR(data->olpc_bat)) |
|---|
| 700 | + return PTR_ERR(data->olpc_bat); |
|---|
| 647 | 701 | |
|---|
| 648 | 702 | if (olpc_ec_wakeup_available()) { |
|---|
| 649 | | - device_set_wakeup_capable(&olpc_ac->dev, true); |
|---|
| 650 | | - device_set_wakeup_capable(&olpc_bat->dev, true); |
|---|
| 703 | + device_set_wakeup_capable(&data->olpc_ac->dev, true); |
|---|
| 704 | + device_set_wakeup_capable(&data->olpc_bat->dev, true); |
|---|
| 651 | 705 | } |
|---|
| 652 | 706 | |
|---|
| 653 | | - return 0; |
|---|
| 654 | | - |
|---|
| 655 | | -error_failed: |
|---|
| 656 | | - device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); |
|---|
| 657 | | -eeprom_failed: |
|---|
| 658 | | - power_supply_unregister(olpc_bat); |
|---|
| 659 | | -battery_failed: |
|---|
| 660 | | - power_supply_unregister(olpc_ac); |
|---|
| 661 | | - return ret; |
|---|
| 662 | | -} |
|---|
| 663 | | - |
|---|
| 664 | | -static int olpc_battery_remove(struct platform_device *pdev) |
|---|
| 665 | | -{ |
|---|
| 666 | | - device_remove_file(&olpc_bat->dev, &olpc_bat_error); |
|---|
| 667 | | - device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom); |
|---|
| 668 | | - power_supply_unregister(olpc_bat); |
|---|
| 669 | | - power_supply_unregister(olpc_ac); |
|---|
| 670 | 707 | return 0; |
|---|
| 671 | 708 | } |
|---|
| 672 | 709 | |
|---|
| 673 | 710 | static const struct of_device_id olpc_battery_ids[] = { |
|---|
| 674 | 711 | { .compatible = "olpc,xo1-battery" }, |
|---|
| 712 | + { .compatible = "olpc,xo1.5-battery" }, |
|---|
| 675 | 713 | {} |
|---|
| 676 | 714 | }; |
|---|
| 677 | 715 | MODULE_DEVICE_TABLE(of, olpc_battery_ids); |
|---|
| .. | .. |
|---|
| 682 | 720 | .of_match_table = olpc_battery_ids, |
|---|
| 683 | 721 | }, |
|---|
| 684 | 722 | .probe = olpc_battery_probe, |
|---|
| 685 | | - .remove = olpc_battery_remove, |
|---|
| 686 | 723 | .suspend = olpc_battery_suspend, |
|---|
| 687 | 724 | }; |
|---|
| 688 | 725 | |
|---|