1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Battery class driver for Apple PMU 4 * 5 * Copyright © 2006 David Woodhouse <dwmw2@infradead.org> 6 */ 7 8 #include <linux/module.h> 9 #include <linux/platform_device.h> 10 #include <linux/err.h> 11 #include <linux/power_supply.h> 12 #include <linux/adb.h> 13 #include <linux/pmu.h> 14 #include <linux/slab.h> 15 16 static struct pmu_battery_dev { 17 struct power_supply *bat; 18 struct power_supply_desc bat_desc; 19 struct pmu_battery_info *pbi; 20 char name[16]; 21 int propval; 22 } *pbats[PMU_MAX_BATTERIES]; 23 24 #define to_pmu_battery_dev(x) power_supply_get_drvdata(x) 25 26 /********************************************************************* 27 * Power 28 *********************************************************************/ 29 30 static int pmu_get_ac_prop(struct power_supply *psy, 31 enum power_supply_property psp, 32 union power_supply_propval *val) 33 { 34 switch (psp) { 35 case POWER_SUPPLY_PROP_ONLINE: 36 val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) || 37 (pmu_battery_count == 0); 38 break; 39 default: 40 return -EINVAL; 41 } 42 43 return 0; 44 } 45 46 static enum power_supply_property pmu_ac_props[] = { 47 POWER_SUPPLY_PROP_ONLINE, 48 }; 49 50 static const struct power_supply_desc pmu_ac_desc = { 51 .name = "pmu-ac", 52 .type = POWER_SUPPLY_TYPE_MAINS, 53 .properties = pmu_ac_props, 54 .num_properties = ARRAY_SIZE(pmu_ac_props), 55 .get_property = pmu_get_ac_prop, 56 }; 57 58 static struct power_supply *pmu_ac; 59 60 /********************************************************************* 61 * Battery properties 62 *********************************************************************/ 63 64 static char *pmu_batt_types[] = { 65 "Smart", "Comet", "Hooper", "Unknown" 66 }; 67 68 static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi) 69 { 70 switch (pbi->flags & PMU_BATT_TYPE_MASK) { 71 case PMU_BATT_TYPE_SMART: 72 return pmu_batt_types[0]; 73 case PMU_BATT_TYPE_COMET: 74 return pmu_batt_types[1]; 75 case PMU_BATT_TYPE_HOOPER: 76 return pmu_batt_types[2]; 77 default: break; 78 } 79 return pmu_batt_types[3]; 80 } 81 82 static int pmu_bat_get_property(struct power_supply *psy, 83 enum power_supply_property psp, 84 union power_supply_propval *val) 85 { 86 struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy); 87 struct pmu_battery_info *pbi = pbat->pbi; 88 89 switch (psp) { 90 case POWER_SUPPLY_PROP_STATUS: 91 if (pbi->flags & PMU_BATT_CHARGING) 92 val->intval = POWER_SUPPLY_STATUS_CHARGING; 93 else if (pmu_power_flags & PMU_PWR_AC_PRESENT) 94 val->intval = POWER_SUPPLY_STATUS_FULL; 95 else 96 val->intval = POWER_SUPPLY_STATUS_DISCHARGING; 97 break; 98 case POWER_SUPPLY_PROP_PRESENT: 99 val->intval = !!(pbi->flags & PMU_BATT_PRESENT); 100 break; 101 case POWER_SUPPLY_PROP_MODEL_NAME: 102 val->strval = pmu_bat_get_model_name(pbi); 103 break; 104 case POWER_SUPPLY_PROP_ENERGY_AVG: 105 val->intval = pbi->charge * 1000; /* mWh -> µWh */ 106 break; 107 case POWER_SUPPLY_PROP_ENERGY_FULL: 108 val->intval = pbi->max_charge * 1000; /* mWh -> µWh */ 109 break; 110 case POWER_SUPPLY_PROP_CURRENT_AVG: 111 val->intval = pbi->amperage * 1000; /* mA -> µA */ 112 break; 113 case POWER_SUPPLY_PROP_VOLTAGE_AVG: 114 val->intval = pbi->voltage * 1000; /* mV -> µV */ 115 break; 116 case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG: 117 val->intval = pbi->time_remaining; 118 break; 119 default: 120 return -EINVAL; 121 } 122 123 return 0; 124 } 125 126 static enum power_supply_property pmu_bat_props[] = { 127 POWER_SUPPLY_PROP_STATUS, 128 POWER_SUPPLY_PROP_PRESENT, 129 POWER_SUPPLY_PROP_MODEL_NAME, 130 POWER_SUPPLY_PROP_ENERGY_AVG, 131 POWER_SUPPLY_PROP_ENERGY_FULL, 132 POWER_SUPPLY_PROP_CURRENT_AVG, 133 POWER_SUPPLY_PROP_VOLTAGE_AVG, 134 POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 135 }; 136 137 /********************************************************************* 138 * Initialisation 139 *********************************************************************/ 140 141 static struct platform_device *bat_pdev; 142 143 static int __init pmu_bat_init(void) 144 { 145 int ret = 0; 146 int i; 147 148 bat_pdev = platform_device_register_simple("pmu-battery", 149 0, NULL, 0); 150 if (IS_ERR(bat_pdev)) { 151 ret = PTR_ERR(bat_pdev); 152 goto pdev_register_failed; 153 } 154 155 pmu_ac = power_supply_register(&bat_pdev->dev, &pmu_ac_desc, NULL); 156 if (IS_ERR(pmu_ac)) { 157 ret = PTR_ERR(pmu_ac); 158 goto ac_register_failed; 159 } 160 161 for (i = 0; i < pmu_battery_count; i++) { 162 struct power_supply_config psy_cfg = {}; 163 struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat), 164 GFP_KERNEL); 165 if (!pbat) 166 break; 167 168 sprintf(pbat->name, "PMU_battery_%d", i); 169 pbat->bat_desc.name = pbat->name; 170 pbat->bat_desc.properties = pmu_bat_props; 171 pbat->bat_desc.num_properties = ARRAY_SIZE(pmu_bat_props); 172 pbat->bat_desc.get_property = pmu_bat_get_property; 173 pbat->pbi = &pmu_batteries[i]; 174 psy_cfg.drv_data = pbat; 175 176 pbat->bat = power_supply_register(&bat_pdev->dev, 177 &pbat->bat_desc, 178 &psy_cfg); 179 if (IS_ERR(pbat->bat)) { 180 ret = PTR_ERR(pbat->bat); 181 kfree(pbat); 182 goto battery_register_failed; 183 } 184 pbats[i] = pbat; 185 } 186 187 goto success; 188 189 battery_register_failed: 190 while (i--) { 191 if (!pbats[i]) 192 continue; 193 power_supply_unregister(pbats[i]->bat); 194 kfree(pbats[i]); 195 } 196 power_supply_unregister(pmu_ac); 197 ac_register_failed: 198 platform_device_unregister(bat_pdev); 199 pdev_register_failed: 200 success: 201 return ret; 202 } 203 204 static void __exit pmu_bat_exit(void) 205 { 206 int i; 207 208 for (i = 0; i < PMU_MAX_BATTERIES; i++) { 209 if (!pbats[i]) 210 continue; 211 power_supply_unregister(pbats[i]->bat); 212 kfree(pbats[i]); 213 } 214 power_supply_unregister(pmu_ac); 215 platform_device_unregister(bat_pdev); 216 } 217 218 module_init(pmu_bat_init); 219 module_exit(pmu_bat_exit); 220 221 MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 222 MODULE_LICENSE("GPL"); 223 MODULE_DESCRIPTION("PMU battery driver"); 224