1/* 2 * Copyright (C) ST-Ericsson SA 2010 3 * Author: Mattias Nilsson <mattias.i.nilsson@stericsson.com> for ST Ericsson. 4 * License terms: GNU General Public License (GPL) version 2 5 */ 6 7#include <linux/err.h> 8#include <linux/module.h> 9#include <linux/platform_device.h> 10#include <linux/pm.h> 11#include <linux/reboot.h> 12#include <linux/signal.h> 13#include <linux/power_supply.h> 14#include <linux/mfd/abx500.h> 15#include <linux/mfd/abx500/ab8500.h> 16#include <linux/mfd/abx500/ab8500-sysctrl.h> 17 18/* RtcCtrl bits */ 19#define AB8500_ALARM_MIN_LOW 0x08 20#define AB8500_ALARM_MIN_MID 0x09 21#define RTC_CTRL 0x0B 22#define RTC_ALARM_ENABLE 0x4 23 24static struct device *sysctrl_dev; 25 26static void ab8500_power_off(void) 27{ 28 sigset_t old; 29 sigset_t all; 30 static char *pss[] = {"ab8500_ac", "pm2301", "ab8500_usb"}; 31 int i; 32 bool charger_present = false; 33 union power_supply_propval val; 34 struct power_supply *psy; 35 int ret; 36 37 if (sysctrl_dev == NULL) { 38 pr_err("%s: sysctrl not initialized\n", __func__); 39 return; 40 } 41 42 /* 43 * If we have a charger connected and we're powering off, 44 * reboot into charge-only mode. 45 */ 46 47 for (i = 0; i < ARRAY_SIZE(pss); i++) { 48 psy = power_supply_get_by_name(pss[i]); 49 if (!psy) 50 continue; 51 52 ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, 53 &val); 54 power_supply_put(psy); 55 56 if (!ret && val.intval) { 57 charger_present = true; 58 break; 59 } 60 } 61 62 if (!charger_present) 63 goto shutdown; 64 65 /* Check if battery is known */ 66 psy = power_supply_get_by_name("ab8500_btemp"); 67 if (psy) { 68 ret = power_supply_get_property(psy, 69 POWER_SUPPLY_PROP_TECHNOLOGY, &val); 70 if (!ret && val.intval != POWER_SUPPLY_TECHNOLOGY_UNKNOWN) { 71 printk(KERN_INFO 72 "Charger \"%s\" is connected with known battery." 73 " Rebooting.\n", 74 pss[i]); 75 machine_restart("charging"); 76 } 77 power_supply_put(psy); 78 } 79 80shutdown: 81 sigfillset(&all); 82 83 if (!sigprocmask(SIG_BLOCK, &all, &old)) { 84 (void)ab8500_sysctrl_set(AB8500_STW4500CTRL1, 85 AB8500_STW4500CTRL1_SWOFF | 86 AB8500_STW4500CTRL1_SWRESET4500N); 87 (void)sigprocmask(SIG_SETMASK, &old, NULL); 88 } 89} 90 91static inline bool valid_bank(u8 bank) 92{ 93 return ((bank == AB8500_SYS_CTRL1_BLOCK) || 94 (bank == AB8500_SYS_CTRL2_BLOCK)); 95} 96 97int ab8500_sysctrl_read(u16 reg, u8 *value) 98{ 99 u8 bank; 100 101 if (sysctrl_dev == NULL) 102 return -EINVAL; 103 104 bank = (reg >> 8); 105 if (!valid_bank(bank)) 106 return -EINVAL; 107 108 return abx500_get_register_interruptible(sysctrl_dev, bank, 109 (u8)(reg & 0xFF), value); 110} 111EXPORT_SYMBOL(ab8500_sysctrl_read); 112 113int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) 114{ 115 u8 bank; 116 117 if (sysctrl_dev == NULL) 118 return -EINVAL; 119 120 bank = (reg >> 8); 121 if (!valid_bank(bank)) 122 return -EINVAL; 123 124 return abx500_mask_and_set_register_interruptible(sysctrl_dev, bank, 125 (u8)(reg & 0xFF), mask, value); 126} 127EXPORT_SYMBOL(ab8500_sysctrl_write); 128 129static int ab8500_sysctrl_probe(struct platform_device *pdev) 130{ 131 struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); 132 struct ab8500_platform_data *plat; 133 struct ab8500_sysctrl_platform_data *pdata; 134 135 plat = dev_get_platdata(pdev->dev.parent); 136 137 if (!plat) 138 return -EINVAL; 139 140 sysctrl_dev = &pdev->dev; 141 142 if (!pm_power_off) 143 pm_power_off = ab8500_power_off; 144 145 pdata = plat->sysctrl; 146 if (pdata) { 147 int last, ret, i, j; 148 149 if (is_ab8505(ab8500)) 150 last = AB8500_SYSCLKREQ4RFCLKBUF; 151 else 152 last = AB8500_SYSCLKREQ8RFCLKBUF; 153 154 for (i = AB8500_SYSCLKREQ1RFCLKBUF; i <= last; i++) { 155 j = i - AB8500_SYSCLKREQ1RFCLKBUF; 156 ret = ab8500_sysctrl_write(i, 0xff, 157 pdata->initial_req_buf_config[j]); 158 dev_dbg(&pdev->dev, 159 "Setting SysClkReq%dRfClkBuf 0x%X\n", 160 j + 1, 161 pdata->initial_req_buf_config[j]); 162 if (ret < 0) { 163 dev_err(&pdev->dev, 164 "unable to set sysClkReq%dRfClkBuf: " 165 "%d\n", j + 1, ret); 166 } 167 } 168 } 169 170 return 0; 171} 172 173static int ab8500_sysctrl_remove(struct platform_device *pdev) 174{ 175 sysctrl_dev = NULL; 176 177 if (pm_power_off == ab8500_power_off) 178 pm_power_off = NULL; 179 180 return 0; 181} 182 183static struct platform_driver ab8500_sysctrl_driver = { 184 .driver = { 185 .name = "ab8500-sysctrl", 186 }, 187 .probe = ab8500_sysctrl_probe, 188 .remove = ab8500_sysctrl_remove, 189}; 190 191static int __init ab8500_sysctrl_init(void) 192{ 193 return platform_driver_register(&ab8500_sysctrl_driver); 194} 195arch_initcall(ab8500_sysctrl_init); 196 197MODULE_AUTHOR("Mattias Nilsson <mattias.i.nilsson@stericsson.com"); 198MODULE_DESCRIPTION("AB8500 system control driver"); 199MODULE_LICENSE("GPL v2"); 200