| /* |
| * Base driver for Marvell 88PM800 |
| * |
| * This file is subject to the terms and conditions of the GNU General |
| * Public License. See the file "COPYING" in the main directory of this |
| * archive for more details. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/i2c.h> |
| #include <linux/switch.h> |
| #include <linux/mfd/core.h> |
| #include <linux/mfd/88pm80x.h> |
| #include <linux/of_device.h> |
| #include <linux/reboot.h> |
| #include <linux/uaccess.h> |
| #include <linux/slab.h> |
| #include <linux/sysctl.h> |
| #include <linux/mfd/pm802.h> |
| #include <linux/mfd/pm813.h> |
| #include <linux/cputype.h> |
| #include <soc/asr/addr-map.h> |
| #include <soc/asr/regs-addr.h> |
| |
| static DEFINE_MUTEX(audio_mode_mutex); |
| struct pm80x_chip *pm80x_chip_g; |
| int ramdump_pm80x_flag; |
| #define PM80X_POWER_PAGE_REG_60 (0x60) |
| #define PM802_BASE_PAGE_REG_15 (0x15) |
| #define PM803_BASE_PAGE_REG_E8 (0xE8) |
| |
| #define PM813_FAULT_WAKEUP_EN (0x1 << 0) |
| #define PM813_FAULT_WAKEUP (0x1 << 3) |
| |
| static DEFINE_MUTEX(pm802_adc_mutex); |
| |
| static void ramdump_pm80x_flag_ctrl(int on) |
| { |
| int data; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type) { |
| /*store the flag on BIT_7 of power page reg 0x60 */ |
| regmap_read(pm80x_chip_g->regmap, |
| PM802_BASE_PAGE_REG_15, &data); |
| if (on) |
| data |= (0x1 << 0); |
| else |
| data &= ~(0x1 << 0); |
| regmap_write(pm80x_chip_g->regmap, |
| PM802_BASE_PAGE_REG_15, data); |
| } else if (CHIP_PM803 == pm80x_chip_g->type) { |
| /*store the flag on BIT_7 of power page reg 0x60 */ |
| regmap_read(pm80x_chip_g->regmap, |
| PM803_BASE_PAGE_REG_E8, &data); |
| if (on) |
| data |= (0x1 << 0); |
| else |
| data &= ~(0x1 << 0); |
| regmap_write(pm80x_chip_g->regmap, |
| PM803_BASE_PAGE_REG_E8, data); |
| } else if (CHIP_PM813 == pm80x_chip_g->type) { |
| /*store the flag on BIT_0 of power page reg 0xF7 */ |
| regmap_read(pm80x_chip_g->regmap, |
| PM813S_RTC_BLANK_REG4, &data); |
| if (on) |
| data |= (0x1 << 0); |
| else |
| data &= ~(0x1 << 0); |
| regmap_write(pm80x_chip_g->regmap, |
| PM813S_RTC_BLANK_REG4, data); |
| } else { |
| /*store the flag on BIT_7 of power page reg 0x60 */ |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM80X_POWER_PAGE_REG_60, &data); |
| if (on) |
| data |= (0x1 << 7); |
| else |
| data &= ~(0x1 << 7); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM80X_POWER_PAGE_REG_60, data); |
| } |
| } |
| |
| int |
| ramdump_pm80x_flag_sysctl(struct ctl_table *table, int write, |
| void __user *buffer, size_t *lenp, |
| loff_t *ppos) |
| { |
| int ret = 0; |
| |
| ret = proc_dointvec(table, write, buffer, lenp, ppos); |
| |
| if (ret || !write) |
| goto out; |
| |
| if (ramdump_pm80x_flag) |
| ramdump_pm80x_flag_ctrl(1); |
| else |
| ramdump_pm80x_flag_ctrl(0); |
| |
| out: |
| return ret; |
| } |
| |
| void buck1_audio_mode_ctrl(int on) |
| { |
| int data; |
| static int refcount; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM803 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) { |
| pr_info("pm802/3/13 buck1 not support audio mode\n"); |
| return; |
| } |
| |
| mutex_lock(&audio_mode_mutex); |
| if (on) { |
| if (refcount == 0) { |
| regmap_read(pm80x_chip_g->regmap, |
| PM800_LOW_POWER2, |
| &data); |
| data |= PM800_AUDIO_MODE_ENA; |
| regmap_write(pm80x_chip_g->regmap, |
| PM800_LOW_POWER2, |
| data); |
| } |
| refcount++; |
| } else { |
| if (refcount == 1) { |
| regmap_read(pm80x_chip_g->regmap, |
| PM800_LOW_POWER2, |
| &data); |
| data &= ~PM800_AUDIO_MODE_ENA; |
| regmap_write(pm80x_chip_g->regmap, |
| PM800_LOW_POWER2, |
| data); |
| } |
| refcount--; |
| } |
| mutex_unlock(&audio_mode_mutex); |
| } |
| EXPORT_SYMBOL(buck1_audio_mode_ctrl); |
| |
| /* set buck1 audio mode voltage as 0.8v*/ |
| void set_buck1_audio_mode_vol(u32 vol) |
| { |
| int data; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM803 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) { |
| pr_info("pm802/3/13 buck1 not support audio mode\n"); |
| return; |
| } |
| |
| /* the proper range is 0~0x54, corresponding voltage is 0.6v~1.8v */ |
| if (vol >= 0x55) |
| return; |
| |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM800_AUDIO_MODE_CONFIG1, |
| &data); |
| data &= ~(0x7f); |
| data |= ((vol & 0x7f) | (0x1 << 7)); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_AUDIO_MODE_CONFIG1, |
| data); |
| } |
| EXPORT_SYMBOL(set_buck1_audio_mode_vol); |
| |
| void buck1_sleepvol_control_for_gps(int on) |
| { |
| int data; |
| static int data_old; |
| static bool get_data_old; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM803 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) { |
| pr_info("skip sleep volt setting for asr pmic\n"); |
| return; |
| } |
| |
| /* |
| * Helan2 need to set buck1 sleep voltage tobe 0.95v when power on gps, |
| * and set back to the old sleep voltage when power off gps. |
| * This function provide such an interface to satisfy it. |
| */ |
| if (!get_data_old) { |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK1_SLEEP, |
| &data_old); |
| get_data_old = true; |
| } |
| if (on) { |
| /* |
| * this means gps power on |
| * buck1 sleep voltage is set to be 0.95v |
| */ |
| data = data_old; |
| data &= ~(0x7f); |
| data |= 0x1c; |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK1_SLEEP, |
| data); |
| } else { |
| data = data_old; |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK1_SLEEP, |
| data); |
| } |
| } |
| EXPORT_SYMBOL(buck1_sleepvol_control_for_gps); |
| |
| static DEFINE_MUTEX(buck2_slp_ctrl_mutex); |
| |
| static void pm802_buck2_sleepmode_control(int on) |
| { |
| int data; |
| static int refcount; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| mutex_lock(&buck2_slp_ctrl_mutex); |
| if (on) { |
| /* |
| * Disable VBUCK2's sleep mode, allow big current output |
| * even system enter to suspend |
| * As SD8787/SLIC use it as 1.8V supply, it would still work |
| * druing suspend and need much current |
| * |
| */ |
| if (refcount == 0) { |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM802_BUCK_SLP2, &data); |
| data = (data & ~PM802_BUCK_SLP_MASK) | |
| (PM802_BUCK_SLP_PWR_ACT2 << PM802_BUCK_SLP_SHIFT); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM802_BUCK_SLP2, |
| data); |
| } |
| refcount++; |
| |
| } else { |
| /* |
| * Enable VBUCK2's sleep mode again (Only 5mA ability) |
| * If SD8787/SLIC is power off, VBUCK2 sleep mode has not side |
| * impact to Sd8787, but has benifit to whole power consumption |
| */ |
| if (refcount == 1) { |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM802_BUCK_SLP2, &data); |
| data = (data & ~PM802_BUCK_SLP_MASK) | |
| (PM802_BUCK_SLP_PWR_LOW << PM802_BUCK_SLP_SHIFT); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM802_BUCK_SLP2, |
| data); |
| } |
| refcount--; |
| } |
| mutex_unlock(&buck2_slp_ctrl_mutex); |
| } |
| |
| static void buck2_sleepmode_control(int on) |
| { |
| int data; |
| static int refcount; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM813 == pm80x_chip_g->type |
| || CHIP_PM803 == pm80x_chip_g->type) { |
| pr_info("skip sleep volt setting for pm813/pm803\n"); |
| return; |
| } |
| |
| mutex_lock(&buck2_slp_ctrl_mutex); |
| if (on) { |
| /* |
| * Disable VBUCK2's sleep mode, allow big current output |
| * even system enter to suspend |
| * As SD8787/SLIC use it as 1.8V supply, it would still work |
| * druing suspend and need much current |
| * |
| */ |
| if (refcount == 0) { |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK_SLP1, &data); |
| data = (data & ~PM800_BUCK2_SLP1_MASK) | |
| (PM800_BUCK_SLP_PWR_ACT2 << PM800_BUCK2_SLP1_SHIFT); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK_SLP1, |
| data); |
| } |
| refcount++; |
| |
| } else { |
| /* |
| * Enable VBUCK2's sleep mode again (Only 5mA ability) |
| * If SD8787/SLIC is power off, VBUCK2 sleep mode has not side |
| * impact to Sd8787, but has benifit to whole power consumption |
| */ |
| if (refcount == 1) { |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK_SLP1, &data); |
| data = (data & ~PM800_BUCK2_SLP1_MASK) | |
| (PM800_BUCK_SLP_PWR_LOW << PM800_BUCK2_SLP1_SHIFT); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_BUCK_SLP1, |
| data); |
| } |
| refcount--; |
| } |
| mutex_unlock(&buck2_slp_ctrl_mutex); |
| } |
| |
| void buck2_sleepmode_control_for_wifi(int on) |
| { |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM803 == pm80x_chip_g->type) |
| return; |
| if (CHIP_PM813 == pm80x_chip_g->type) |
| return; |
| |
| if (CHIP_PM802 == pm80x_chip_g->type) |
| pm802_buck2_sleepmode_control(on); |
| else |
| buck2_sleepmode_control(on); |
| } |
| EXPORT_SYMBOL(buck2_sleepmode_control_for_wifi); |
| |
| static void ldo8_sleepmode_control(int on) |
| { |
| int data; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return; |
| } |
| |
| if (CHIP_PM801 == pm80x_chip_g->type || |
| CHIP_PM802 == pm80x_chip_g->type || |
| CHIP_PM803 == pm80x_chip_g->type) |
| return; |
| |
| if (on) { |
| /* Disable LDO8's sleep mode */ |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM800_LDO_SLP2, &data); |
| data |= (0x3 << 6); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_LDO_SLP2, |
| data); |
| |
| } else { |
| /* Enable LDO8's sleep mode */ |
| regmap_read(pm80x_chip_g->subchip->regmap_power, |
| PM800_LDO_SLP2, &data); |
| data = (data & ~(0x3 << 6)) | (0x2 << 6); |
| regmap_write(pm80x_chip_g->subchip->regmap_power, |
| PM800_LDO_SLP2, |
| data); |
| } |
| } |
| |
| void buck2_ldo8_sleepmode_control_for_slic(int on) |
| { |
| buck2_sleepmode_control(on); |
| ldo8_sleepmode_control(on); |
| } |
| EXPORT_SYMBOL(buck2_ldo8_sleepmode_control_for_slic); |
| |
| /* return gpadc voltage */ |
| int get_gpadc_volt(struct pm80x_chip *chip, int gpadc_id) |
| { |
| int ret, volt; |
| unsigned char buf[2]; |
| int gp_meas; |
| |
| switch (gpadc_id) { |
| case PM800_GPADC0: |
| gp_meas = PM800_GPADC0_MEAS1; |
| break; |
| case PM800_GPADC1: |
| gp_meas = PM800_GPADC1_MEAS1; |
| break; |
| case PM800_GPADC2: |
| gp_meas = PM800_GPADC2_MEAS1; |
| break; |
| case PM800_GPADC3: |
| gp_meas = PM800_GPADC3_MEAS1; |
| break; |
| default: |
| dev_err(chip->dev, "get GPADC failed!\n"); |
| return -EINVAL; |
| |
| } |
| |
| ret = regmap_bulk_read(chip->subchip->regmap_gpadc, |
| gp_meas, buf, 2); |
| if (ret < 0) { |
| dev_err(chip->dev, "Attention: failed to get volt!\n"); |
| return -EINVAL; |
| } |
| |
| volt = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); |
| dev_dbg(chip->dev, "%s: volt value = 0x%x\n", __func__, volt); |
| /* volt = value * 1.4 * 1000 / (2^12) */ |
| volt = ((volt & 0xfff) * 7 * 100) >> 11; |
| dev_dbg(chip->dev, "%s: voltage = %dmV\n", __func__, volt); |
| |
| return volt; |
| } |
| |
| /* return voltage via bias current from GPADC */ |
| static int get_gpadc_bias_volt(struct pm80x_chip *chip, int gpadc_id, int bias) |
| { |
| int volt, data, gp_bias, gp_bias_shift; |
| |
| switch (gpadc_id) { |
| case PM800_GPADC0: |
| gp_bias = PM800_GPADC_BIAS1; |
| gp_bias_shift = 0; |
| break; |
| case PM800_GPADC1: |
| gp_bias = PM800_GPADC_BIAS2; |
| gp_bias_shift = 0; |
| break; |
| case PM800_GPADC2: |
| gp_bias = PM800_GPADC_BIAS3; |
| gp_bias_shift = 0; |
| break; |
| case PM800_GPADC3: |
| gp_bias = PM800_GPADC_BIAS4; |
| gp_bias_shift = 0; |
| break; |
| default: |
| dev_err(chip->dev, "get GPADC failed!\n"); |
| return -EINVAL; |
| } |
| |
| /* get the register value */ |
| if (bias > 76) |
| bias = 76; |
| if (bias < 1) |
| bias = 1; |
| bias = (bias - 1) / 5; |
| |
| regmap_read(chip->subchip->regmap_gpadc, gp_bias, &data); |
| data &= ~(0xF << gp_bias_shift); |
| data |= (bias << gp_bias_shift); |
| regmap_write(chip->subchip->regmap_gpadc, gp_bias, data); |
| /* wait for voltage to be stable */ |
| usleep_range(30, 40); |
| |
| volt = get_gpadc_volt(chip, gpadc_id); |
| if ((volt < 0) || (volt > 1400)) { |
| dev_err(chip->dev, "%s return %dmV\n", __func__, volt); |
| return -EINVAL; |
| } |
| |
| return volt; |
| } |
| |
| static int pm801_get_batt_vol(struct pm80x_chip *chip, int *data) |
| { |
| int ret; |
| unsigned char buf[2]; |
| if (!data) |
| return -EINVAL; |
| |
| if (chip->type == CHIP_PM801) { |
| ret = regmap_bulk_read(chip->subchip->regmap_gpadc, |
| PM801_GPADC_VLDOIN_AVG1, buf, 2); |
| if (ret < 0) |
| return ret; |
| } else { |
| ret = regmap_bulk_read(chip->subchip->regmap_gpadc, |
| PM800_VBAT_AVG, buf, 2); |
| if (ret < 0) |
| return ret; |
| } |
| |
| *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); |
| /* measure(mv) = value * 4 * 1.4 *1000/(2^12) */ |
| *data = ((*data & 0xfff) * 7 * 100) >> 9; |
| /* |
| * Calibrate vbat measurement only for un-trimed PMIC |
| * VBATmeas = VBATreal * gain + offset, so |
| * VBATreal = (VBATmeas - offset)/ gain; |
| * According to our test of vbat_sleep in bootup, the calculated |
| * gain as below: |
| * For Aruba 0.1, offset is -13.4ma, gain is 1.008. |
| * For Aruba 0.2, offset = -0.0087V and gain=1.007. |
| */ |
| /* data = (*data + 9) * 1000/1007; */ |
| return 0; |
| } |
| |
| static int pm802_get_batt_vol(struct pm80x_chip *chip, int *data) |
| { |
| int ret = 0; |
| unsigned char buf[2]; |
| u32 adc_channel = 15; |
| |
| if (!data) |
| return -EINVAL; |
| |
| might_sleep(); |
| |
| mutex_lock(&pm802_adc_mutex); |
| regmap_update_bits(chip->subchip->regmap_gpadc, PM802_GPADC_CFG3, 0x7, ((adc_channel >> 3) & 0x3)); |
| regmap_update_bits(chip->subchip->regmap_gpadc, PM802_GPADC_CFG2, (0x7 << 5), ((adc_channel & 0x7) << 5)); |
| regmap_update_bits(chip->subchip->regmap_gpadc, PM802_GPADC_CFG3, (0x3 << 2), (0x3 << 2)); |
| usleep_range(40, 40); |
| |
| if (CHIP_PM813 == chip->type) |
| adc_channel = 5; |
| |
| if (CHIP_PM813S_A1_ID == chip->chip_id |
| || CHIP_PM813S_A0_ID == chip->chip_id) |
| ret = regmap_bulk_read(chip->subchip->regmap_gpadc, |
| PM813S_VLDOIN_MEAS1, buf, 2); |
| else |
| ret = regmap_bulk_read(chip->subchip->regmap_gpadc, |
| PM802_VLDOIN_MEAS1, buf, 2); |
| if (ret < 0) |
| goto out_ret; |
| |
| if (CHIP_PM813 == chip->type) |
| *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); |
| else |
| *data = ((buf[1] & 0x0f) << 8) | (buf[0] & 0xff); |
| |
| /* measure(mv) = value * 6.5 *1000/(2^12) */ |
| *data = (*data - chip->adc_sample_offset) * (13000 + chip->adc_vref_offset); |
| |
| if (CHIP_PM813S_A1_ID == chip->chip_id |
| || CHIP_PM813S_A0_ID == chip->chip_id) |
| *data = (*data >> 6) / (128 * 1); |
| else |
| *data = (*data >> 6) / (129 * 1); |
| |
| if (*data > 6500) |
| *data = 6500; |
| |
| if (*data < 0) |
| *data = 0; |
| ret = 0; |
| |
| out_ret: |
| mutex_unlock(&pm802_adc_mutex); |
| return ret; |
| } |
| |
| /* return gpadc voltage */ |
| int pm802_get_gpadc_volt(struct pm80x_chip *chip, int gpadc_id) |
| { |
| int ret, volt; |
| unsigned char buf[2]; |
| int gp_meas; |
| u32 adc_channel; |
| |
| might_sleep(); |
| mutex_lock(&pm802_adc_mutex); |
| switch (gpadc_id) { |
| case PM802_GPADC0: |
| gp_meas = PM802_GPADC0_MEAS1; |
| if (CHIP_PM813S_A1_ID == chip->chip_id |
| || CHIP_PM813S_A0_ID == chip->chip_id) |
| gp_meas = PM813S_GPADC0_MEAS1; |
| |
| if (CHIP_PM813 == chip->type) |
| adc_channel = 28; |
| else |
| adc_channel = 0x7; |
| break; |
| case PM802_GPADC1: |
| gp_meas = PM802_GPADC1_MEAS1; |
| if (CHIP_PM813S_A1_ID == chip->chip_id |
| || CHIP_PM813S_A0_ID == chip->chip_id) |
| gp_meas = PM813S_GPADC1_MEAS1; |
| |
| if (CHIP_PM813 == chip->type) |
| adc_channel = 9; |
| else |
| adc_channel = 0x8; |
| break; |
| default: |
| dev_err(chip->dev, "get GPADC failed!\n"); |
| mutex_unlock(&pm802_adc_mutex); |
| return -EINVAL; |
| |
| } |
| |
| regmap_update_bits(chip->subchip->regmap_gpadc, PM802_GPADC_CFG3, 0x7, ((adc_channel >> 3) & 0x3)); |
| regmap_update_bits(chip->subchip->regmap_gpadc, PM802_GPADC_CFG2, (0x7 << 5), ((adc_channel & 0x7) << 5)); |
| regmap_update_bits(chip->subchip->regmap_gpadc, PM802_GPADC_CFG3, (0x3 << 2), (0x3 << 2)); |
| usleep_range(40, 40); |
| |
| ret = regmap_bulk_read(chip->subchip->regmap_gpadc, |
| gp_meas, buf, 2); |
| if (ret < 0) { |
| dev_err(chip->dev, "Attention: failed to get volt!\n"); |
| mutex_unlock(&pm802_adc_mutex); |
| return -EINVAL; |
| } |
| if (CHIP_PM813 == chip->type) |
| volt = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); |
| else |
| volt = ((buf[1] & 0x0f) << 8) | (buf[0] & 0xff); |
| |
| dev_dbg(chip->dev, "%s: volt value = 0x%x\n", __func__, volt); |
| |
| /* volt = value * 1.3 * 1000 / (2^12) */ |
| /* volt = ((volt & 0xfff) * 650) >> 11; */ |
| if (CHIP_PM813 == chip->type) { |
| if (gpadc_id == PM802_GPADC0) { |
| volt = (volt - chip->adc_sample_offset) * (13000 + chip->adc_vref_offset); |
| if (CHIP_PM813S_A1_ID == chip->chip_id |
| || CHIP_PM813S_A0_ID == chip->chip_id) |
| volt = (volt >> 6) / (128 * 5); |
| else |
| volt = (volt >> 6) / (129 * 5); |
| |
| if (volt < 0) |
| volt = 0; |
| if (volt > 1300) |
| volt = 1300; |
| } else { |
| volt = (volt - chip->adc_sample_offset - 0x800) * (13000 + chip->adc_vref_offset); |
| if (CHIP_PM813S_A1_ID == chip->chip_id |
| || CHIP_PM813S_A0_ID == chip->chip_id) |
| volt = (volt >> 5) / (128 * 5); |
| else |
| volt = (volt >> 5) / (129 * 5); |
| if (volt < -1300) |
| volt = -1300; |
| if (volt > 1300) |
| volt = 1300; |
| } |
| } else { |
| volt = (volt - chip->adc_sample_offset) * (13000 + chip->adc_vref_offset); |
| |
| volt = (volt >> 6) / (129 * 5); |
| if (volt > 1300) |
| volt = 1300; |
| if (volt < 0) |
| volt = 0; |
| } |
| |
| dev_dbg(chip->dev, "%s: voltage = %dmV\n", __func__, volt); |
| |
| mutex_unlock(&pm802_adc_mutex); |
| return volt; |
| } |
| |
| /* return voltage via bias current from GPADC */ |
| static int pm802_get_gpadc_bias_volt(struct pm80x_chip *chip, int gpadc_id, int bias) |
| { |
| int volt, data, gp_bias, gp_bias_shift; |
| |
| switch (gpadc_id) { |
| case PM802_GPADC0: |
| gp_bias = PM802_GPADC_BIAS1; |
| gp_bias_shift = 0; |
| break; |
| case PM802_GPADC1: |
| gp_bias = PM802_GPADC_BIAS2; |
| gp_bias_shift = 4; |
| break; |
| default: |
| dev_err(chip->dev, "get GPADC failed!\n"); |
| return -EINVAL; |
| } |
| /* get the register value */ |
| if (bias > 76) |
| bias = 76; |
| if (bias < 1) |
| bias = 1; |
| bias = (bias - 1) / 5; |
| |
| regmap_read(chip->subchip->regmap_gpadc, gp_bias, &data); |
| data &= ~(0xF << gp_bias_shift); |
| data |= (bias << gp_bias_shift); |
| regmap_write(chip->subchip->regmap_gpadc, gp_bias, data); |
| /* wait for voltage to be stable */ |
| usleep_range(30, 40); |
| |
| volt = pm802_get_gpadc_volt(chip, gpadc_id); |
| if ((volt < 0) || (volt > 1300)) { |
| dev_err(chip->dev, "%s return %dmV\n", __func__, volt); |
| return -EINVAL; |
| } |
| |
| return volt; |
| } |
| |
| #ifdef CONFIG_ASR_AUXADC |
| #define ASR_IPC_BIAS_EN (0x1 << 26) |
| #define APB_SPARE14_REG (0x134) |
| static int asr_ipc_set_bias(int gpadc_id, int bias) |
| { |
| u32 bias_mask, bias_val, bias_shift, val2; |
| void __iomem *apbs_base = regs_addr_get_va(REGS_ADDR_APBS); |
| |
| if (bias > 30 || bias < 0 || (bias % 2) || gpadc_id > ASR_AUXADC3) { |
| pr_err("error: error bias value!, gp: %d, bias: %d\n", gpadc_id, bias); |
| return -EINVAL; |
| } |
| |
| bias_shift = 8 + (gpadc_id - ASR_AUXADC1) * 4; |
| bias_mask = 0xf << bias_shift; |
| bias_val = (bias / 2) << bias_shift; |
| |
| val2 = readl(apbs_base + APB_SPARE14_REG); |
| val2 &= ~(bias_mask); |
| val2 |= bias_val; |
| val2 |= ASR_IPC_BIAS_EN; |
| writel(val2, apbs_base + APB_SPARE14_REG); |
| usleep_range(1000, 1000); |
| return 0; |
| } |
| |
| static int asr_ipc_clear_bias(int gpadc_id, int bias) |
| { |
| u32 bias_mask, bias_val, bias_shift, val2; |
| void __iomem *apbs_base = regs_addr_get_va(REGS_ADDR_APBS); |
| |
| bias_shift = 8 + (gpadc_id - ASR_AUXADC1) * 4; |
| bias_mask = 0xf << bias_shift; |
| bias_val = 0; |
| |
| val2 = readl(apbs_base + APB_SPARE14_REG); |
| val2 &= ~(bias_mask); |
| val2 |= bias_val; |
| val2 |= ASR_IPC_BIAS_EN; |
| writel(val2, apbs_base + APB_SPARE14_REG); |
| return 0; |
| } |
| |
| /* return voltage via bias current from GPADC */ |
| static int get_auxadc_bias_volt(struct pm80x_chip *chip, int gpadc_id, int bias) |
| { |
| int volt; |
| |
| if (asr_ipc_set_bias(gpadc_id, bias) < 0) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -EINVAL; |
| } |
| |
| volt = extern_get_auxadc_volt(gpadc_id); |
| asr_ipc_clear_bias(gpadc_id, bias); |
| return volt; |
| } |
| #endif |
| |
| /* |
| * used by non-pmic driver |
| * TODO: remove later |
| */ |
| int extern_get_gpadc_bias_volt(int gpadc_id, int bias) |
| { |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -ENODEV; |
| } |
| |
| if (cpu_is_asr1903()) { |
| #ifdef CONFIG_ASR_AUXADC |
| if (gpadc_id >= ASR_AUXADC1 && gpadc_id <= ASR_AUXADC5) |
| return get_auxadc_bias_volt(pm80x_chip_g, gpadc_id, bias); |
| else if (CHIP_PM803 == pm80x_chip_g->type) |
| return -ENODEV; |
| #else |
| if (CHIP_PM803 == pm80x_chip_g->type) |
| return -ENODEV; |
| #endif |
| } else { |
| if (CHIP_PM803 == pm80x_chip_g->type) |
| return -ENODEV; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) |
| return pm802_get_gpadc_bias_volt(pm80x_chip_g, gpadc_id, bias); |
| else |
| return get_gpadc_bias_volt(pm80x_chip_g, gpadc_id, bias); |
| } |
| EXPORT_SYMBOL(extern_get_gpadc_bias_volt); |
| |
| /* |
| * used by non-pmic driver |
| */ |
| int extern_get_gpadc_volt(int gpadc_id) |
| { |
| #ifdef CONFIG_MFD_PM803 |
| int value, ret; |
| #endif |
| |
| #ifdef CONFIG_ASR_AUXADC |
| if (gpadc_id >= ASR_AUXADC1 && gpadc_id <= ASR_AUXADC5) |
| return extern_get_auxadc_volt(gpadc_id); |
| #endif |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -ENODEV; |
| } |
| |
| #if defined(CONFIG_MFD_PM803) && defined(CONFIG_POWER_SUPPLY) |
| if (CHIP_PM803 == pm80x_chip_g->type) { |
| ret = pm803_get_adc_mvolt(gpadc_id, &value); |
| if (ret < 0) |
| return ret; |
| else |
| return value; |
| } |
| #endif |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) |
| return pm802_get_gpadc_volt(pm80x_chip_g, gpadc_id); |
| else |
| return get_gpadc_volt(pm80x_chip_g, gpadc_id); |
| } |
| EXPORT_SYMBOL(extern_get_gpadc_volt); |
| |
| /* |
| * used by non-pmic driver |
| */ |
| int extern_get_batt_volt(int *data) |
| { |
| if (!pm80x_chip_g) { |
| pr_err("%s: error: pm80x_chip_g not inited\n", __func__); |
| return -ENODEV; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) { |
| return pm802_get_batt_vol(pm80x_chip_g, data); |
| } else if (CHIP_PM800 == pm80x_chip_g->type |
| || CHIP_PM801 == pm80x_chip_g->type) { |
| return pm801_get_batt_vol(pm80x_chip_g, data); |
| } else { |
| pr_err("%s unsupported pmic: %d\n", __func__, pm80x_chip_g->type); |
| return -EINVAL; |
| } |
| } |
| EXPORT_SYMBOL(extern_get_batt_volt); |
| |
| /* |
| * used by non-pmic driver |
| */ |
| int extern_pmic_base_read(u32 reg, u32 *value) |
| { |
| int ret; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -ENODEV; |
| } |
| |
| ret = regmap_read(pm80x_chip_g->regmap, |
| reg, |
| value); |
| return ret; |
| } |
| EXPORT_SYMBOL(extern_pmic_base_read); |
| |
| /* |
| * used by non-pmic driver |
| */ |
| int extern_pmic_base_write(u32 reg, u32 value) |
| { |
| int ret; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -ENODEV; |
| } |
| |
| ret = regmap_write(pm80x_chip_g->regmap, |
| reg, |
| value); |
| return ret; |
| } |
| EXPORT_SYMBOL(extern_pmic_base_write); |
| |
| int pmic_rw_reg(u8 reg, u8 mask, u8 value) |
| { |
| int ret; |
| u8 data, buf[2]; |
| struct i2c_client *client; |
| struct i2c_msg msgs[2]; |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -ENODEV; |
| } |
| |
| client = pm80x_chip_g->client; |
| msgs[0].addr = client->addr, |
| msgs[0].flags = 0, |
| msgs[0].len = 1, |
| msgs[0].buf = buf, |
| |
| msgs[1].addr = client->addr, |
| msgs[1].flags = I2C_M_RD, |
| msgs[1].len = 1, |
| msgs[1].buf = &data, |
| |
| /* |
| * I2C pins may be in non-AP pinstate, and __i2c_transfer |
| * won't change it back to AP pinstate like i2c_transfer, |
| * so change i2c pins to AP pinstate explicitly here. |
| */ |
| i2c_pxa_set_pinstate(client->adapter, "default"); |
| |
| /* |
| * set i2c to pio mode |
| * for in power off sequence, irq has been disabled |
| */ |
| i2c_set_pio_mode(client->adapter, 1); |
| |
| buf[0] = reg; |
| ret = __i2c_transfer(client->adapter, msgs, 2); |
| if (ret < 0) { |
| pr_err("%s read register fails...\n", __func__); |
| WARN_ON(1); |
| return ret; |
| } |
| /* issue SW power down */ |
| msgs[0].addr = client->addr; |
| msgs[0].flags = 0; |
| msgs[0].len = 2; |
| msgs[0].buf[0] = reg; |
| msgs[0].buf[1] = ((data & (~mask)) | value); |
| ret = __i2c_transfer(client->adapter, msgs, 1); |
| if (ret < 0) { |
| pr_err("%s write data fails: ret = %d\n", __func__, ret); |
| WARN_ON(1); |
| } |
| return ret; |
| |
| } |
| |
| static int __pmic_reset(void) |
| { |
| int ret = 0; |
| |
| /* pmic reset contain two parts: fault wake up and sw_powerdown */ |
| pr_info("pmic power down/up reset....\n"); |
| |
| if (!pm80x_chip_g) { |
| pr_err("error: pm80x_chip_g not inited\n"); |
| return -ENODEV; |
| } |
| |
| if (CHIP_PM802 == pm80x_chip_g->type |
| || CHIP_PM803 == pm80x_chip_g->type |
| || CHIP_PM813 == pm80x_chip_g->type) { |
| pr_info("asr pmic reset\n"); |
| if (CHIP_PM802S_ID_A0 == pm80x_chip_g->chip_id |
| || CHIP_PM802S_ID_A1 == pm80x_chip_g->chip_id |
| || CHIP_PM802S_ID_A2 == pm80x_chip_g->chip_id |
| || CHIP_PM813S_A1_ID == pm80x_chip_g->chip_id |
| || CHIP_PM813S_A0_ID == pm80x_chip_g->chip_id) { |
| /* discharge timer should be set greater than or equal to 65ms */ |
| ret = pmic_rw_reg(PM802_RTC_MISC2, 0xf, 0x3); |
| if (ret < 0) { |
| pr_err("%s, set PM802_RTC_MISC2 fail", __func__); |
| return ret; |
| } |
| } else { |
| /* discharge timer should be set greater than or equal to 1s */ |
| ret = pmic_rw_reg(PM802_RTC_MISC2, 0xf, 0x1); |
| if (ret < 0) { |
| pr_err("%s, set PM802_RTC_MISC2 fail", __func__); |
| return ret; |
| } |
| } |
| |
| ret = pmic_rw_reg(PM802_RTC_MISC4, (0x1 << 1), 0x0); |
| if (ret < 0) { |
| pr_err("%s, set PM802_RTC_MISC4 fail", __func__); |
| return ret; |
| } |
| } |
| |
| if (CHIP_PM813S_A1_ID == pm80x_chip_g->chip_id |
| || CHIP_PM813S_A0_ID == pm80x_chip_g->chip_id |
| || CHIP_PM803 == pm80x_chip_g->type) { |
| ret = pmic_rw_reg(PM800_RTC_MISC5, PM803_FAULT_WAKEUP_EN, PM803_FAULT_WAKEUP_EN); |
| if (ret < 0) { |
| pr_err("%s, enbale pm803 fault wakeup en fail!", __func__); |
| return ret; |
| } |
| ret = pmic_rw_reg(PM800_RTC_MISC5, PM803_FAULT_WAKEUP, PM803_FAULT_WAKEUP); |
| if (ret < 0) { |
| pr_err("%s, enbale pm803 fault wakeup fail!", __func__); |
| return ret; |
| } |
| } else if (CHIP_PM813 == pm80x_chip_g->type) { |
| ret = pmic_rw_reg(PM800_RTC_MISC5, PM813_FAULT_WAKEUP_EN, PM813_FAULT_WAKEUP_EN); |
| if (ret < 0) { |
| pr_err("%s, enbale pm813 fault wakeup en fail!", __func__); |
| return ret; |
| } |
| ret = pmic_rw_reg(PM800_RTC_MISC5, PM813_FAULT_WAKEUP, PM813_FAULT_WAKEUP); |
| if (ret < 0) { |
| pr_err("%s, enbale pm813 fault wakeup fail!", __func__); |
| return ret; |
| } |
| } else { |
| ret = pmic_rw_reg(PM800_RTC_MISC5, PM800_FAULT_WAKEUP_EN, PM800_FAULT_WAKEUP_EN); |
| if (ret < 0) { |
| pr_err("%s, enbale pmic fault wakeup en fail!", __func__); |
| return ret; |
| } |
| |
| ret = pmic_rw_reg(PM800_RTC_MISC5, PM800_FAULT_WAKEUP, PM800_FAULT_WAKEUP); |
| if (ret < 0) { |
| pr_err("%s, enbale pmic fault wakeup fail!", __func__); |
| return ret; |
| } |
| } |
| |
| /* Issue SW power down */ |
| ret = pmic_rw_reg(PM800_WAKEUP1, PM800_SW_PDOWN, PM800_SW_PDOWN); |
| if (ret < 0) |
| pr_err("%s, set PM800_SW_PDOWN fail\n", __func__); |
| |
| return ret; |
| } |
| |
| void pmic_reset(void) |
| { |
| int ret; |
| |
| /* try 3 times to reduce the fail rate */ |
| pr_info("PMIC reset....\n"); |
| ret = __pmic_reset(); |
| if (ret < 0) |
| ret = __pmic_reset(); |
| if (ret < 0) |
| ret = __pmic_reset(); |
| if (ret < 0) |
| pr_err("%s, pmic reset failed\n", __func__); |
| } |