xref: /rk3399_rockchip-uboot/board/freescale/common/vid.c (revision 00caae6d47645e68d6e5277aceb69592b49381a6)
13ad2737eSYing Zhang /*
23ad2737eSYing Zhang  * Copyright 2014 Freescale Semiconductor, Inc.
33ad2737eSYing Zhang  *
43ad2737eSYing Zhang  * SPDX-License-Identifier:     GPL-2.0+
53ad2737eSYing Zhang  */
63ad2737eSYing Zhang 
73ad2737eSYing Zhang #include <common.h>
83ad2737eSYing Zhang #include <command.h>
93ad2737eSYing Zhang #include <i2c.h>
1002b5d2edSShaohui Xie #include <asm/io.h>
11126fe70dSShaohui Xie #ifdef CONFIG_FSL_LSCH2
1202b5d2edSShaohui Xie #include <asm/arch/immap_lsch2.h>
13ed2530d0SRai Harninder #elif defined(CONFIG_FSL_LSCH3)
14ed2530d0SRai Harninder #include <asm/arch/immap_lsch3.h>
1502b5d2edSShaohui Xie #else
163ad2737eSYing Zhang #include <asm/immap_85xx.h>
1702b5d2edSShaohui Xie #endif
183ad2737eSYing Zhang #include "vid.h"
193ad2737eSYing Zhang 
203ad2737eSYing Zhang DECLARE_GLOBAL_DATA_PTR;
213ad2737eSYing Zhang 
i2c_multiplexer_select_vid_channel(u8 channel)223ad2737eSYing Zhang int __weak i2c_multiplexer_select_vid_channel(u8 channel)
233ad2737eSYing Zhang {
243ad2737eSYing Zhang 	return 0;
253ad2737eSYing Zhang }
263ad2737eSYing Zhang 
273ad2737eSYing Zhang /*
283ad2737eSYing Zhang  * Compensate for a board specific voltage drop between regulator and SoC
293ad2737eSYing Zhang  * return a value in mV
303ad2737eSYing Zhang  */
board_vdd_drop_compensation(void)313ad2737eSYing Zhang int __weak board_vdd_drop_compensation(void)
323ad2737eSYing Zhang {
333ad2737eSYing Zhang 	return 0;
343ad2737eSYing Zhang }
353ad2737eSYing Zhang 
363ad2737eSYing Zhang /*
373ad2737eSYing Zhang  * Get the i2c address configuration for the IR regulator chip
383ad2737eSYing Zhang  *
393ad2737eSYing Zhang  * There are some variance in the RDB HW regarding the I2C address configuration
403ad2737eSYing Zhang  * for the IR regulator chip, which is likely a problem of external resistor
413ad2737eSYing Zhang  * accuracy. So we just check each address in a hopefully non-intrusive mode
423ad2737eSYing Zhang  * and use the first one that seems to work
433ad2737eSYing Zhang  *
443ad2737eSYing Zhang  * The IR chip can show up under the following addresses:
453ad2737eSYing Zhang  * 0x08 (Verified on T1040RDB-PA,T4240RDB-PB,X-T4240RDB-16GPA)
463ad2737eSYing Zhang  * 0x09 (Verified on T1040RDB-PA)
472f66a828SYing Zhang  * 0x38 (Verified on T2080QDS, T2081QDS, T4240RDB)
483ad2737eSYing Zhang  */
find_ir_chip_on_i2c(void)493ad2737eSYing Zhang static int find_ir_chip_on_i2c(void)
503ad2737eSYing Zhang {
513ad2737eSYing Zhang 	int i2caddress;
523ad2737eSYing Zhang 	int ret;
533ad2737eSYing Zhang 	u8 byte;
543ad2737eSYing Zhang 	int i;
553ad2737eSYing Zhang 	const int ir_i2c_addr[] = {0x38, 0x08, 0x09};
563ad2737eSYing Zhang 
573ad2737eSYing Zhang 	/* Check all the address */
583ad2737eSYing Zhang 	for (i = 0; i < (sizeof(ir_i2c_addr)/sizeof(ir_i2c_addr[0])); i++) {
593ad2737eSYing Zhang 		i2caddress = ir_i2c_addr[i];
603ad2737eSYing Zhang 		ret = i2c_read(i2caddress,
613ad2737eSYing Zhang 			       IR36021_MFR_ID_OFFSET, 1, (void *)&byte,
623ad2737eSYing Zhang 			       sizeof(byte));
633ad2737eSYing Zhang 		if ((ret >= 0) && (byte == IR36021_MFR_ID))
643ad2737eSYing Zhang 			return i2caddress;
653ad2737eSYing Zhang 	}
663ad2737eSYing Zhang 	return -1;
673ad2737eSYing Zhang }
683ad2737eSYing Zhang 
693ad2737eSYing Zhang /* Maximum loop count waiting for new voltage to take effect */
703ad2737eSYing Zhang #define MAX_LOOP_WAIT_NEW_VOL		100
713ad2737eSYing Zhang /* Maximum loop count waiting for the voltage to be stable */
723ad2737eSYing Zhang #define MAX_LOOP_WAIT_VOL_STABLE	100
733ad2737eSYing Zhang /*
743ad2737eSYing Zhang  * read_voltage from sensor on I2C bus
753ad2737eSYing Zhang  * We use average of 4 readings, waiting for WAIT_FOR_ADC before
763ad2737eSYing Zhang  * another reading
773ad2737eSYing Zhang  */
783ad2737eSYing Zhang #define NUM_READINGS    4       /* prefer to be power of 2 for efficiency */
793ad2737eSYing Zhang 
803ad2737eSYing Zhang /* If an INA220 chip is available, we can use it to read back the voltage
813ad2737eSYing Zhang  * as it may have a higher accuracy than the IR chip for the same purpose
823ad2737eSYing Zhang  */
833ad2737eSYing Zhang #ifdef CONFIG_VOL_MONITOR_INA220
843ad2737eSYing Zhang #define WAIT_FOR_ADC	532	/* wait for 532 microseconds for ADC */
853ad2737eSYing Zhang #define ADC_MIN_ACCURACY	4
863ad2737eSYing Zhang #else
873ad2737eSYing Zhang #define WAIT_FOR_ADC	138	/* wait for 138 microseconds for ADC */
883ad2737eSYing Zhang #define ADC_MIN_ACCURACY	4
893ad2737eSYing Zhang #endif
903ad2737eSYing Zhang 
913ad2737eSYing Zhang #ifdef CONFIG_VOL_MONITOR_INA220
read_voltage_from_INA220(int i2caddress)923ad2737eSYing Zhang static int read_voltage_from_INA220(int i2caddress)
933ad2737eSYing Zhang {
943ad2737eSYing Zhang 	int i, ret, voltage_read = 0;
953ad2737eSYing Zhang 	u16 vol_mon;
963ad2737eSYing Zhang 	u8 buf[2];
973ad2737eSYing Zhang 
983ad2737eSYing Zhang 	for (i = 0; i < NUM_READINGS; i++) {
993ad2737eSYing Zhang 		ret = i2c_read(I2C_VOL_MONITOR_ADDR,
1003ad2737eSYing Zhang 			       I2C_VOL_MONITOR_BUS_V_OFFSET, 1,
1013ad2737eSYing Zhang 			       (void *)&buf, 2);
1023ad2737eSYing Zhang 		if (ret) {
1033ad2737eSYing Zhang 			printf("VID: failed to read core voltage\n");
1043ad2737eSYing Zhang 			return ret;
1053ad2737eSYing Zhang 		}
1063ad2737eSYing Zhang 		vol_mon = (buf[0] << 8) | buf[1];
1073ad2737eSYing Zhang 		if (vol_mon & I2C_VOL_MONITOR_BUS_V_OVF) {
1083ad2737eSYing Zhang 			printf("VID: Core voltage sensor error\n");
1093ad2737eSYing Zhang 			return -1;
1103ad2737eSYing Zhang 		}
1113ad2737eSYing Zhang 		debug("VID: bus voltage reads 0x%04x\n", vol_mon);
1123ad2737eSYing Zhang 		/* LSB = 4mv */
1133ad2737eSYing Zhang 		voltage_read += (vol_mon >> I2C_VOL_MONITOR_BUS_V_SHIFT) * 4;
1143ad2737eSYing Zhang 		udelay(WAIT_FOR_ADC);
1153ad2737eSYing Zhang 	}
1163ad2737eSYing Zhang 	/* calculate the average */
1173ad2737eSYing Zhang 	voltage_read /= NUM_READINGS;
1183ad2737eSYing Zhang 
1193ad2737eSYing Zhang 	return voltage_read;
1203ad2737eSYing Zhang }
1213ad2737eSYing Zhang #endif
1223ad2737eSYing Zhang 
1233ad2737eSYing Zhang /* read voltage from IR */
1243ad2737eSYing Zhang #ifdef CONFIG_VOL_MONITOR_IR36021_READ
read_voltage_from_IR(int i2caddress)1253ad2737eSYing Zhang static int read_voltage_from_IR(int i2caddress)
1263ad2737eSYing Zhang {
1273ad2737eSYing Zhang 	int i, ret, voltage_read = 0;
1283ad2737eSYing Zhang 	u16 vol_mon;
1293ad2737eSYing Zhang 	u8 buf;
1303ad2737eSYing Zhang 
1313ad2737eSYing Zhang 	for (i = 0; i < NUM_READINGS; i++) {
1323ad2737eSYing Zhang 		ret = i2c_read(i2caddress,
1333ad2737eSYing Zhang 			       IR36021_LOOP1_VOUT_OFFSET,
1343ad2737eSYing Zhang 			       1, (void *)&buf, 1);
1353ad2737eSYing Zhang 		if (ret) {
1363ad2737eSYing Zhang 			printf("VID: failed to read vcpu\n");
1373ad2737eSYing Zhang 			return ret;
1383ad2737eSYing Zhang 		}
1393ad2737eSYing Zhang 		vol_mon = buf;
1403ad2737eSYing Zhang 		if (!vol_mon) {
1413ad2737eSYing Zhang 			printf("VID: Core voltage sensor error\n");
1423ad2737eSYing Zhang 			return -1;
1433ad2737eSYing Zhang 		}
1443ad2737eSYing Zhang 		debug("VID: bus voltage reads 0x%02x\n", vol_mon);
1453ad2737eSYing Zhang 		/* Resolution is 1/128V. We scale up here to get 1/128mV
1463ad2737eSYing Zhang 		 * and divide at the end
1473ad2737eSYing Zhang 		 */
1483ad2737eSYing Zhang 		voltage_read += vol_mon * 1000;
1493ad2737eSYing Zhang 		udelay(WAIT_FOR_ADC);
1503ad2737eSYing Zhang 	}
1513ad2737eSYing Zhang 	/* Scale down to the real mV as IR resolution is 1/128V, rounding up */
1523ad2737eSYing Zhang 	voltage_read = DIV_ROUND_UP(voltage_read, 128);
1533ad2737eSYing Zhang 
1543ad2737eSYing Zhang 	/* calculate the average */
1553ad2737eSYing Zhang 	voltage_read /= NUM_READINGS;
1563ad2737eSYing Zhang 
1573ad2737eSYing Zhang 	/* Compensate for a board specific voltage drop between regulator and
1583ad2737eSYing Zhang 	 * SoC before converting into an IR VID value
1593ad2737eSYing Zhang 	 */
1603ad2737eSYing Zhang 	voltage_read -= board_vdd_drop_compensation();
1613ad2737eSYing Zhang 
1623ad2737eSYing Zhang 	return voltage_read;
1633ad2737eSYing Zhang }
1643ad2737eSYing Zhang #endif
1653ad2737eSYing Zhang 
read_voltage(int i2caddress)1663ad2737eSYing Zhang static int read_voltage(int i2caddress)
1673ad2737eSYing Zhang {
1683ad2737eSYing Zhang 	int voltage_read;
1693ad2737eSYing Zhang #ifdef CONFIG_VOL_MONITOR_INA220
1703ad2737eSYing Zhang 	voltage_read = read_voltage_from_INA220(i2caddress);
1713ad2737eSYing Zhang #elif defined CONFIG_VOL_MONITOR_IR36021_READ
1723ad2737eSYing Zhang 	voltage_read = read_voltage_from_IR(i2caddress);
1733ad2737eSYing Zhang #else
1743ad2737eSYing Zhang 	return -1;
1753ad2737eSYing Zhang #endif
1763ad2737eSYing Zhang 	return voltage_read;
1773ad2737eSYing Zhang }
1783ad2737eSYing Zhang 
1793ad2737eSYing Zhang /*
1803ad2737eSYing Zhang  * We need to calculate how long before the voltage stops to drop
1813ad2737eSYing Zhang  * or increase. It returns with the loop count. Each loop takes
1823ad2737eSYing Zhang  * several readings (WAIT_FOR_ADC)
1833ad2737eSYing Zhang  */
wait_for_new_voltage(int vdd,int i2caddress)1843ad2737eSYing Zhang static int wait_for_new_voltage(int vdd, int i2caddress)
1853ad2737eSYing Zhang {
1863ad2737eSYing Zhang 	int timeout, vdd_current;
1873ad2737eSYing Zhang 
1883ad2737eSYing Zhang 	vdd_current = read_voltage(i2caddress);
1893ad2737eSYing Zhang 	/* wait until voltage starts to reach the target. Voltage slew
1903ad2737eSYing Zhang 	 * rates by typical regulators will always lead to stable readings
1913ad2737eSYing Zhang 	 * within each fairly long ADC interval in comparison to the
1923ad2737eSYing Zhang 	 * intended voltage delta change until the target voltage is
1933ad2737eSYing Zhang 	 * reached. The fairly small voltage delta change to any target
1943ad2737eSYing Zhang 	 * VID voltage also means that this function will always complete
1953ad2737eSYing Zhang 	 * within few iterations. If the timeout was ever reached, it would
1963ad2737eSYing Zhang 	 * point to a serious failure in the regulator system.
1973ad2737eSYing Zhang 	 */
1983ad2737eSYing Zhang 	for (timeout = 0;
1993ad2737eSYing Zhang 	     abs(vdd - vdd_current) > (IR_VDD_STEP_UP + IR_VDD_STEP_DOWN) &&
2003ad2737eSYing Zhang 	     timeout < MAX_LOOP_WAIT_NEW_VOL; timeout++) {
2013ad2737eSYing Zhang 		vdd_current = read_voltage(i2caddress);
2023ad2737eSYing Zhang 	}
2033ad2737eSYing Zhang 	if (timeout >= MAX_LOOP_WAIT_NEW_VOL) {
2043ad2737eSYing Zhang 		printf("VID: Voltage adjustment timeout\n");
2053ad2737eSYing Zhang 		return -1;
2063ad2737eSYing Zhang 	}
2073ad2737eSYing Zhang 	return timeout;
2083ad2737eSYing Zhang }
2093ad2737eSYing Zhang 
2103ad2737eSYing Zhang /*
2113ad2737eSYing Zhang  * this function keeps reading the voltage until it is stable or until the
2123ad2737eSYing Zhang  * timeout expires
2133ad2737eSYing Zhang  */
wait_for_voltage_stable(int i2caddress)2143ad2737eSYing Zhang static int wait_for_voltage_stable(int i2caddress)
2153ad2737eSYing Zhang {
2163ad2737eSYing Zhang 	int timeout, vdd_current, vdd;
2173ad2737eSYing Zhang 
2183ad2737eSYing Zhang 	vdd = read_voltage(i2caddress);
2193ad2737eSYing Zhang 	udelay(NUM_READINGS * WAIT_FOR_ADC);
2203ad2737eSYing Zhang 
2213ad2737eSYing Zhang 	/* wait until voltage is stable */
2223ad2737eSYing Zhang 	vdd_current = read_voltage(i2caddress);
2233ad2737eSYing Zhang 	/* The maximum timeout is
2243ad2737eSYing Zhang 	 * MAX_LOOP_WAIT_VOL_STABLE * NUM_READINGS * WAIT_FOR_ADC
2253ad2737eSYing Zhang 	 */
2263ad2737eSYing Zhang 	for (timeout = MAX_LOOP_WAIT_VOL_STABLE;
2273ad2737eSYing Zhang 	     abs(vdd - vdd_current) > ADC_MIN_ACCURACY &&
2283ad2737eSYing Zhang 	     timeout > 0; timeout--) {
2293ad2737eSYing Zhang 		vdd = vdd_current;
2303ad2737eSYing Zhang 		udelay(NUM_READINGS * WAIT_FOR_ADC);
2313ad2737eSYing Zhang 		vdd_current = read_voltage(i2caddress);
2323ad2737eSYing Zhang 	}
2333ad2737eSYing Zhang 	if (timeout == 0)
2343ad2737eSYing Zhang 		return -1;
2353ad2737eSYing Zhang 	return vdd_current;
2363ad2737eSYing Zhang }
2373ad2737eSYing Zhang 
2383ad2737eSYing Zhang #ifdef CONFIG_VOL_MONITOR_IR36021_SET
2393ad2737eSYing Zhang /* Set the voltage to the IR chip */
set_voltage_to_IR(int i2caddress,int vdd)2403ad2737eSYing Zhang static int set_voltage_to_IR(int i2caddress, int vdd)
2413ad2737eSYing Zhang {
2423ad2737eSYing Zhang 	int wait, vdd_last;
2433ad2737eSYing Zhang 	int ret;
2443ad2737eSYing Zhang 	u8 vid;
2453ad2737eSYing Zhang 
2463ad2737eSYing Zhang 	/* Compensate for a board specific voltage drop between regulator and
2473ad2737eSYing Zhang 	 * SoC before converting into an IR VID value
2483ad2737eSYing Zhang 	 */
2493ad2737eSYing Zhang 	vdd += board_vdd_drop_compensation();
250126fe70dSShaohui Xie #ifdef CONFIG_FSL_LSCH2
25102b5d2edSShaohui Xie 	vid = DIV_ROUND_UP(vdd - 265, 5);
25202b5d2edSShaohui Xie #else
2533ad2737eSYing Zhang 	vid = DIV_ROUND_UP(vdd - 245, 5);
25402b5d2edSShaohui Xie #endif
2553ad2737eSYing Zhang 
2563ad2737eSYing Zhang 	ret = i2c_write(i2caddress, IR36021_LOOP1_MANUAL_ID_OFFSET,
2573ad2737eSYing Zhang 			1, (void *)&vid, sizeof(vid));
2583ad2737eSYing Zhang 	if (ret) {
2593ad2737eSYing Zhang 		printf("VID: failed to write VID\n");
2603ad2737eSYing Zhang 		return -1;
2613ad2737eSYing Zhang 	}
2623ad2737eSYing Zhang 	wait = wait_for_new_voltage(vdd, i2caddress);
2633ad2737eSYing Zhang 	if (wait < 0)
2643ad2737eSYing Zhang 		return -1;
2653ad2737eSYing Zhang 	debug("VID: Waited %d us\n", wait * NUM_READINGS * WAIT_FOR_ADC);
2663ad2737eSYing Zhang 
2673ad2737eSYing Zhang 	vdd_last = wait_for_voltage_stable(i2caddress);
2683ad2737eSYing Zhang 	if (vdd_last < 0)
2693ad2737eSYing Zhang 		return -1;
2703ad2737eSYing Zhang 	debug("VID: Current voltage is %d mV\n", vdd_last);
2713ad2737eSYing Zhang 	return vdd_last;
2723ad2737eSYing Zhang }
2733ad2737eSYing Zhang #endif
2743ad2737eSYing Zhang 
set_voltage(int i2caddress,int vdd)2753ad2737eSYing Zhang static int set_voltage(int i2caddress, int vdd)
2763ad2737eSYing Zhang {
2773ad2737eSYing Zhang 	int vdd_last = -1;
2783ad2737eSYing Zhang 
2793ad2737eSYing Zhang #ifdef CONFIG_VOL_MONITOR_IR36021_SET
2803ad2737eSYing Zhang 	vdd_last = set_voltage_to_IR(i2caddress, vdd);
2813ad2737eSYing Zhang #else
2823ad2737eSYing Zhang 	#error Specific voltage monitor must be defined
2833ad2737eSYing Zhang #endif
2843ad2737eSYing Zhang 	return vdd_last;
2853ad2737eSYing Zhang }
2863ad2737eSYing Zhang 
28729ca713cSPriyanka Jain #ifdef CONFIG_FSL_LSCH3
adjust_vdd(ulong vdd_override)2883ad2737eSYing Zhang int adjust_vdd(ulong vdd_override)
2893ad2737eSYing Zhang {
2903ad2737eSYing Zhang 	int re_enable = disable_interrupts();
29129ca713cSPriyanka Jain 	struct ccsr_gur *gur = (void *)(CONFIG_SYS_FSL_GUTS_ADDR);
29229ca713cSPriyanka Jain 	u32 fusesr;
29329ca713cSPriyanka Jain 	u8 vid, buf;
29429ca713cSPriyanka Jain 	int vdd_target, vdd_current, vdd_last;
29529ca713cSPriyanka Jain 	int ret, i2caddress;
29629ca713cSPriyanka Jain 	unsigned long vdd_string_override;
29729ca713cSPriyanka Jain 	char *vdd_string;
29829ca713cSPriyanka Jain 	static const uint16_t vdd[32] = {
29929ca713cSPriyanka Jain 		10500,
30029ca713cSPriyanka Jain 		0,      /* reserved */
30129ca713cSPriyanka Jain 		9750,
30229ca713cSPriyanka Jain 		0,      /* reserved */
30329ca713cSPriyanka Jain 		9500,
30429ca713cSPriyanka Jain 		0,      /* reserved */
30529ca713cSPriyanka Jain 		0,      /* reserved */
30629ca713cSPriyanka Jain 		0,      /* reserved */
30729ca713cSPriyanka Jain 		0,      /* reserved */
30829ca713cSPriyanka Jain 		0,      /* reserved */
30929ca713cSPriyanka Jain 		0,      /* reserved */
31029ca713cSPriyanka Jain 		0,      /* reserved */
31129ca713cSPriyanka Jain 		0,      /* reserved */
31229ca713cSPriyanka Jain 		0,      /* reserved */
31329ca713cSPriyanka Jain 		0,      /* reserved */
31429ca713cSPriyanka Jain 		0,      /* reserved */
31529ca713cSPriyanka Jain 		10000,  /* 1.0000V */
31629ca713cSPriyanka Jain 		0,      /* reserved */
31729ca713cSPriyanka Jain 		10250,
31829ca713cSPriyanka Jain 		0,      /* reserved */
31929ca713cSPriyanka Jain 		10500,
32029ca713cSPriyanka Jain 		0,      /* reserved */
32129ca713cSPriyanka Jain 		0,      /* reserved */
32229ca713cSPriyanka Jain 		0,      /* reserved */
32329ca713cSPriyanka Jain 		0,      /* reserved */
32429ca713cSPriyanka Jain 		0,      /* reserved */
32529ca713cSPriyanka Jain 		0,      /* reserved */
32629ca713cSPriyanka Jain 		0,      /* reserved */
32729ca713cSPriyanka Jain 		0,      /* reserved */
32829ca713cSPriyanka Jain 		0,      /* reserved */
32929ca713cSPriyanka Jain 		0,      /* reserved */
33029ca713cSPriyanka Jain 		0,      /* reserved */
33129ca713cSPriyanka Jain 	};
33229ca713cSPriyanka Jain 	struct vdd_drive {
33329ca713cSPriyanka Jain 		u8 vid;
33429ca713cSPriyanka Jain 		unsigned voltage;
33529ca713cSPriyanka Jain 	};
33629ca713cSPriyanka Jain 
33729ca713cSPriyanka Jain 	ret = i2c_multiplexer_select_vid_channel(I2C_MUX_CH_VOL_MONITOR);
33829ca713cSPriyanka Jain 	if (ret) {
33929ca713cSPriyanka Jain 		debug("VID: I2C failed to switch channel\n");
34029ca713cSPriyanka Jain 		ret = -1;
34129ca713cSPriyanka Jain 		goto exit;
34229ca713cSPriyanka Jain 	}
34329ca713cSPriyanka Jain 	ret = find_ir_chip_on_i2c();
34429ca713cSPriyanka Jain 	if (ret < 0) {
34529ca713cSPriyanka Jain 		printf("VID: Could not find voltage regulator on I2C.\n");
34629ca713cSPriyanka Jain 		ret = -1;
34729ca713cSPriyanka Jain 		goto exit;
34829ca713cSPriyanka Jain 	} else {
34929ca713cSPriyanka Jain 		i2caddress = ret;
35029ca713cSPriyanka Jain 		debug("VID: IR Chip found on I2C address 0x%02x\n", i2caddress);
35129ca713cSPriyanka Jain 	}
35229ca713cSPriyanka Jain 
35329ca713cSPriyanka Jain 	/* check IR chip work on Intel mode*/
35429ca713cSPriyanka Jain 	ret = i2c_read(i2caddress,
35529ca713cSPriyanka Jain 		       IR36021_INTEL_MODE_OOFSET,
35629ca713cSPriyanka Jain 		       1, (void *)&buf, 1);
35729ca713cSPriyanka Jain 	if (ret) {
35829ca713cSPriyanka Jain 		printf("VID: failed to read IR chip mode.\n");
35929ca713cSPriyanka Jain 		ret = -1;
36029ca713cSPriyanka Jain 		goto exit;
36129ca713cSPriyanka Jain 	}
36229ca713cSPriyanka Jain 	if ((buf & IR36021_MODE_MASK) != IR36021_INTEL_MODE) {
36329ca713cSPriyanka Jain 		printf("VID: IR Chip is not used in Intel mode.\n");
36429ca713cSPriyanka Jain 		ret = -1;
36529ca713cSPriyanka Jain 		goto exit;
36629ca713cSPriyanka Jain 	}
36729ca713cSPriyanka Jain 
36829ca713cSPriyanka Jain 	/* get the voltage ID from fuse status register */
36929ca713cSPriyanka Jain 	fusesr = in_le32(&gur->dcfg_fusesr);
37029ca713cSPriyanka Jain 	vid = (fusesr >> FSL_CHASSIS3_DCFG_FUSESR_ALTVID_SHIFT) &
37129ca713cSPriyanka Jain 		FSL_CHASSIS3_DCFG_FUSESR_ALTVID_MASK;
37229ca713cSPriyanka Jain 	if ((vid == 0) || (vid == FSL_CHASSIS3_DCFG_FUSESR_ALTVID_MASK)) {
37329ca713cSPriyanka Jain 		vid = (fusesr >> FSL_CHASSIS3_DCFG_FUSESR_VID_SHIFT) &
37429ca713cSPriyanka Jain 			FSL_CHASSIS3_DCFG_FUSESR_VID_MASK;
37529ca713cSPriyanka Jain 	}
37629ca713cSPriyanka Jain 	vdd_target = vdd[vid];
37729ca713cSPriyanka Jain 
37829ca713cSPriyanka Jain 	/* check override variable for overriding VDD */
379*00caae6dSSimon Glass 	vdd_string = env_get(CONFIG_VID_FLS_ENV);
38029ca713cSPriyanka Jain 	if (vdd_override == 0 && vdd_string &&
38129ca713cSPriyanka Jain 	    !strict_strtoul(vdd_string, 10, &vdd_string_override))
38229ca713cSPriyanka Jain 		vdd_override = vdd_string_override;
38329ca713cSPriyanka Jain 
38429ca713cSPriyanka Jain 	if (vdd_override >= VDD_MV_MIN && vdd_override <= VDD_MV_MAX) {
38529ca713cSPriyanka Jain 		vdd_target = vdd_override * 10; /* convert to 1/10 mV */
38629ca713cSPriyanka Jain 		debug("VDD override is %lu\n", vdd_override);
38729ca713cSPriyanka Jain 	} else if (vdd_override != 0) {
38829ca713cSPriyanka Jain 		printf("Invalid value.\n");
38929ca713cSPriyanka Jain 	}
39029ca713cSPriyanka Jain 
39129ca713cSPriyanka Jain 	/* divide and round up by 10 to get a value in mV */
39229ca713cSPriyanka Jain 	vdd_target = DIV_ROUND_UP(vdd_target, 10);
39329ca713cSPriyanka Jain 	if (vdd_target == 0) {
39429ca713cSPriyanka Jain 		debug("VID: VID not used\n");
39529ca713cSPriyanka Jain 		ret = 0;
39629ca713cSPriyanka Jain 		goto exit;
39729ca713cSPriyanka Jain 	} else if (vdd_target < VDD_MV_MIN || vdd_target > VDD_MV_MAX) {
39829ca713cSPriyanka Jain 		/* Check vdd_target is in valid range */
39929ca713cSPriyanka Jain 		printf("VID: Target VID %d mV is not in range.\n",
40029ca713cSPriyanka Jain 		       vdd_target);
40129ca713cSPriyanka Jain 		ret = -1;
40229ca713cSPriyanka Jain 		goto exit;
40329ca713cSPriyanka Jain 	} else {
40429ca713cSPriyanka Jain 		debug("VID: vid = %d mV\n", vdd_target);
40529ca713cSPriyanka Jain 	}
40629ca713cSPriyanka Jain 
40729ca713cSPriyanka Jain 	/*
40829ca713cSPriyanka Jain 	 * Read voltage monitor to check real voltage.
40929ca713cSPriyanka Jain 	 */
41029ca713cSPriyanka Jain 	vdd_last = read_voltage(i2caddress);
41129ca713cSPriyanka Jain 	if (vdd_last < 0) {
41229ca713cSPriyanka Jain 		printf("VID: Couldn't read sensor abort VID adjustment\n");
41329ca713cSPriyanka Jain 		ret = -1;
41429ca713cSPriyanka Jain 		goto exit;
41529ca713cSPriyanka Jain 	}
41629ca713cSPriyanka Jain 	vdd_current = vdd_last;
41729ca713cSPriyanka Jain 	debug("VID: Core voltage is currently at %d mV\n", vdd_last);
41829ca713cSPriyanka Jain 	/*
41929ca713cSPriyanka Jain 	  * Adjust voltage to at or one step above target.
42029ca713cSPriyanka Jain 	  * As measurements are less precise than setting the values
42129ca713cSPriyanka Jain 	  * we may run through dummy steps that cancel each other
42229ca713cSPriyanka Jain 	  * when stepping up and then down.
42329ca713cSPriyanka Jain 	  */
42429ca713cSPriyanka Jain 	while (vdd_last > 0 &&
42529ca713cSPriyanka Jain 	       vdd_last < vdd_target) {
42629ca713cSPriyanka Jain 		vdd_current += IR_VDD_STEP_UP;
42729ca713cSPriyanka Jain 		vdd_last = set_voltage(i2caddress, vdd_current);
42829ca713cSPriyanka Jain 	}
42929ca713cSPriyanka Jain 	while (vdd_last > 0 &&
43029ca713cSPriyanka Jain 	       vdd_last > vdd_target + (IR_VDD_STEP_DOWN - 1)) {
43129ca713cSPriyanka Jain 		vdd_current -= IR_VDD_STEP_DOWN;
43229ca713cSPriyanka Jain 		vdd_last = set_voltage(i2caddress, vdd_current);
43329ca713cSPriyanka Jain 	}
43429ca713cSPriyanka Jain 
43529ca713cSPriyanka Jain 	if (vdd_last > 0)
43629ca713cSPriyanka Jain 		printf("VID: Core voltage after adjustment is at %d mV\n",
43729ca713cSPriyanka Jain 		       vdd_last);
43829ca713cSPriyanka Jain 	else
43929ca713cSPriyanka Jain 		ret = -1;
44029ca713cSPriyanka Jain exit:
44129ca713cSPriyanka Jain 	if (re_enable)
44229ca713cSPriyanka Jain 		enable_interrupts();
44329ca713cSPriyanka Jain 	i2c_multiplexer_select_vid_channel(I2C_MUX_CH_DEFAULT);
44429ca713cSPriyanka Jain 	return ret;
44529ca713cSPriyanka Jain }
44629ca713cSPriyanka Jain #else /* !CONFIG_FSL_LSCH3 */
adjust_vdd(ulong vdd_override)44729ca713cSPriyanka Jain int adjust_vdd(ulong vdd_override)
44829ca713cSPriyanka Jain {
44929ca713cSPriyanka Jain 	int re_enable = disable_interrupts();
45029ca713cSPriyanka Jain #if defined(CONFIG_FSL_LSCH2)
45102b5d2edSShaohui Xie 	struct ccsr_gur *gur = (void *)(CONFIG_SYS_FSL_GUTS_ADDR);
45202b5d2edSShaohui Xie #else
4533ad2737eSYing Zhang 	ccsr_gur_t __iomem *gur =
4543ad2737eSYing Zhang 		(void __iomem *)(CONFIG_SYS_MPC85xx_GUTS_ADDR);
45502b5d2edSShaohui Xie #endif
4563ad2737eSYing Zhang 	u32 fusesr;
457cabe4d2fSYing Zhang 	u8 vid, buf;
4583ad2737eSYing Zhang 	int vdd_target, vdd_current, vdd_last;
4593ad2737eSYing Zhang 	int ret, i2caddress;
4603ad2737eSYing Zhang 	unsigned long vdd_string_override;
4613ad2737eSYing Zhang 	char *vdd_string;
4623ad2737eSYing Zhang 	static const uint16_t vdd[32] = {
4633ad2737eSYing Zhang 		0,      /* unused */
4643ad2737eSYing Zhang 		9875,   /* 0.9875V */
4653ad2737eSYing Zhang 		9750,
4663ad2737eSYing Zhang 		9625,
4673ad2737eSYing Zhang 		9500,
4683ad2737eSYing Zhang 		9375,
4693ad2737eSYing Zhang 		9250,
4703ad2737eSYing Zhang 		9125,
4713ad2737eSYing Zhang 		9000,
4723ad2737eSYing Zhang 		8875,
4733ad2737eSYing Zhang 		8750,
4743ad2737eSYing Zhang 		8625,
4753ad2737eSYing Zhang 		8500,
4763ad2737eSYing Zhang 		8375,
4773ad2737eSYing Zhang 		8250,
4783ad2737eSYing Zhang 		8125,
4793ad2737eSYing Zhang 		10000,  /* 1.0000V */
4803ad2737eSYing Zhang 		10125,
4813ad2737eSYing Zhang 		10250,
4823ad2737eSYing Zhang 		10375,
4833ad2737eSYing Zhang 		10500,
4843ad2737eSYing Zhang 		10625,
4853ad2737eSYing Zhang 		10750,
4863ad2737eSYing Zhang 		10875,
4873ad2737eSYing Zhang 		11000,
4883ad2737eSYing Zhang 		0,      /* reserved */
4893ad2737eSYing Zhang 	};
4903ad2737eSYing Zhang 	struct vdd_drive {
4913ad2737eSYing Zhang 		u8 vid;
4923ad2737eSYing Zhang 		unsigned voltage;
4933ad2737eSYing Zhang 	};
4943ad2737eSYing Zhang 
4953ad2737eSYing Zhang 	ret = i2c_multiplexer_select_vid_channel(I2C_MUX_CH_VOL_MONITOR);
4963ad2737eSYing Zhang 	if (ret) {
4973ad2737eSYing Zhang 		debug("VID: I2C failed to switch channel\n");
4983ad2737eSYing Zhang 		ret = -1;
4993ad2737eSYing Zhang 		goto exit;
5003ad2737eSYing Zhang 	}
5013ad2737eSYing Zhang 	ret = find_ir_chip_on_i2c();
5023ad2737eSYing Zhang 	if (ret < 0) {
5033ad2737eSYing Zhang 		printf("VID: Could not find voltage regulator on I2C.\n");
5043ad2737eSYing Zhang 		ret = -1;
5053ad2737eSYing Zhang 		goto exit;
5063ad2737eSYing Zhang 	} else {
5073ad2737eSYing Zhang 		i2caddress = ret;
5083ad2737eSYing Zhang 		debug("VID: IR Chip found on I2C address 0x%02x\n", i2caddress);
5093ad2737eSYing Zhang 	}
5103ad2737eSYing Zhang 
511cabe4d2fSYing Zhang 	/* check IR chip work on Intel mode*/
512cabe4d2fSYing Zhang 	ret = i2c_read(i2caddress,
513cabe4d2fSYing Zhang 		       IR36021_INTEL_MODE_OOFSET,
514cabe4d2fSYing Zhang 		       1, (void *)&buf, 1);
515cabe4d2fSYing Zhang 	if (ret) {
516cabe4d2fSYing Zhang 		printf("VID: failed to read IR chip mode.\n");
517cabe4d2fSYing Zhang 		ret = -1;
518cabe4d2fSYing Zhang 		goto exit;
519cabe4d2fSYing Zhang 	}
520cabe4d2fSYing Zhang 	if ((buf & IR36021_MODE_MASK) != IR36021_INTEL_MODE) {
521cabe4d2fSYing Zhang 		printf("VID: IR Chip is not used in Intel mode.\n");
522cabe4d2fSYing Zhang 		ret = -1;
523cabe4d2fSYing Zhang 		goto exit;
524cabe4d2fSYing Zhang 	}
525cabe4d2fSYing Zhang 
5263ad2737eSYing Zhang 	/* get the voltage ID from fuse status register */
5273ad2737eSYing Zhang 	fusesr = in_be32(&gur->dcfg_fusesr);
5283ad2737eSYing Zhang 	/*
5293ad2737eSYing Zhang 	 * VID is used according to the table below
5303ad2737eSYing Zhang 	 *                ---------------------------------------
5313ad2737eSYing Zhang 	 *                |                DA_V                 |
5323ad2737eSYing Zhang 	 *                |-------------------------------------|
5333ad2737eSYing Zhang 	 *                | 5b00000 | 5b00001-5b11110 | 5b11111 |
5343ad2737eSYing Zhang 	 * ---------------+---------+-----------------+---------|
5353ad2737eSYing Zhang 	 * | D | 5b00000  | NO VID  | VID = DA_V      | NO VID  |
5363ad2737eSYing Zhang 	 * | A |----------+---------+-----------------+---------|
5373ad2737eSYing Zhang 	 * | _ | 5b00001  |VID =    | VID =           |VID =    |
5383ad2737eSYing Zhang 	 * | V |   ~      | DA_V_ALT|   DA_V_ALT      | DA_A_VLT|
5393ad2737eSYing Zhang 	 * | _ | 5b11110  |         |                 |         |
5403ad2737eSYing Zhang 	 * | A |----------+---------+-----------------+---------|
5413ad2737eSYing Zhang 	 * | L | 5b11111  | No VID  | VID = DA_V      | NO VID  |
5423ad2737eSYing Zhang 	 * | T |          |         |                 |         |
5433ad2737eSYing Zhang 	 * ------------------------------------------------------
5443ad2737eSYing Zhang 	 */
545126fe70dSShaohui Xie #ifdef CONFIG_FSL_LSCH2
54602b5d2edSShaohui Xie 	vid = (fusesr >> FSL_CHASSIS2_DCFG_FUSESR_ALTVID_SHIFT) &
54702b5d2edSShaohui Xie 		FSL_CHASSIS2_DCFG_FUSESR_ALTVID_MASK;
54802b5d2edSShaohui Xie 	if ((vid == 0) || (vid == FSL_CHASSIS2_DCFG_FUSESR_ALTVID_MASK)) {
54902b5d2edSShaohui Xie 		vid = (fusesr >> FSL_CHASSIS2_DCFG_FUSESR_VID_SHIFT) &
55002b5d2edSShaohui Xie 			FSL_CHASSIS2_DCFG_FUSESR_VID_MASK;
55102b5d2edSShaohui Xie 	}
55202b5d2edSShaohui Xie #else
5533ad2737eSYing Zhang 	vid = (fusesr >> FSL_CORENET_DCFG_FUSESR_ALTVID_SHIFT) &
5543ad2737eSYing Zhang 		FSL_CORENET_DCFG_FUSESR_ALTVID_MASK;
5553ad2737eSYing Zhang 	if ((vid == 0) || (vid == FSL_CORENET_DCFG_FUSESR_ALTVID_MASK)) {
5563ad2737eSYing Zhang 		vid = (fusesr >> FSL_CORENET_DCFG_FUSESR_VID_SHIFT) &
5573ad2737eSYing Zhang 			FSL_CORENET_DCFG_FUSESR_VID_MASK;
5583ad2737eSYing Zhang 	}
55902b5d2edSShaohui Xie #endif
5603ad2737eSYing Zhang 	vdd_target = vdd[vid];
5613ad2737eSYing Zhang 
5623ad2737eSYing Zhang 	/* check override variable for overriding VDD */
563*00caae6dSSimon Glass 	vdd_string = env_get(CONFIG_VID_FLS_ENV);
5643ad2737eSYing Zhang 	if (vdd_override == 0 && vdd_string &&
5653ad2737eSYing Zhang 	    !strict_strtoul(vdd_string, 10, &vdd_string_override))
5663ad2737eSYing Zhang 		vdd_override = vdd_string_override;
5673ad2737eSYing Zhang 	if (vdd_override >= VDD_MV_MIN && vdd_override <= VDD_MV_MAX) {
5683ad2737eSYing Zhang 		vdd_target = vdd_override * 10; /* convert to 1/10 mV */
5693ad2737eSYing Zhang 		debug("VDD override is %lu\n", vdd_override);
5703ad2737eSYing Zhang 	} else if (vdd_override != 0) {
5713ad2737eSYing Zhang 		printf("Invalid value.\n");
5723ad2737eSYing Zhang 	}
5733ad2737eSYing Zhang 	if (vdd_target == 0) {
5743ad2737eSYing Zhang 		debug("VID: VID not used\n");
5753ad2737eSYing Zhang 		ret = 0;
5763ad2737eSYing Zhang 		goto exit;
5773ad2737eSYing Zhang 	} else {
5783ad2737eSYing Zhang 		/* divide and round up by 10 to get a value in mV */
5793ad2737eSYing Zhang 		vdd_target = DIV_ROUND_UP(vdd_target, 10);
5803ad2737eSYing Zhang 		debug("VID: vid = %d mV\n", vdd_target);
5813ad2737eSYing Zhang 	}
5823ad2737eSYing Zhang 
5833ad2737eSYing Zhang 	/*
5843ad2737eSYing Zhang 	 * Read voltage monitor to check real voltage.
5853ad2737eSYing Zhang 	 */
5863ad2737eSYing Zhang 	vdd_last = read_voltage(i2caddress);
5873ad2737eSYing Zhang 	if (vdd_last < 0) {
5883ad2737eSYing Zhang 		printf("VID: Couldn't read sensor abort VID adjustment\n");
5893ad2737eSYing Zhang 		ret = -1;
5903ad2737eSYing Zhang 		goto exit;
5913ad2737eSYing Zhang 	}
5923ad2737eSYing Zhang 	vdd_current = vdd_last;
5933ad2737eSYing Zhang 	debug("VID: Core voltage is currently at %d mV\n", vdd_last);
5943ad2737eSYing Zhang 	/*
5953ad2737eSYing Zhang 	  * Adjust voltage to at or one step above target.
5963ad2737eSYing Zhang 	  * As measurements are less precise than setting the values
5973ad2737eSYing Zhang 	  * we may run through dummy steps that cancel each other
5983ad2737eSYing Zhang 	  * when stepping up and then down.
5993ad2737eSYing Zhang 	  */
6003ad2737eSYing Zhang 	while (vdd_last > 0 &&
6013ad2737eSYing Zhang 	       vdd_last < vdd_target) {
6023ad2737eSYing Zhang 		vdd_current += IR_VDD_STEP_UP;
6033ad2737eSYing Zhang 		vdd_last = set_voltage(i2caddress, vdd_current);
6043ad2737eSYing Zhang 	}
6053ad2737eSYing Zhang 	while (vdd_last > 0 &&
6063ad2737eSYing Zhang 	       vdd_last > vdd_target + (IR_VDD_STEP_DOWN - 1)) {
6073ad2737eSYing Zhang 		vdd_current -= IR_VDD_STEP_DOWN;
6083ad2737eSYing Zhang 		vdd_last = set_voltage(i2caddress, vdd_current);
6093ad2737eSYing Zhang 	}
6103ad2737eSYing Zhang 
6113ad2737eSYing Zhang 	if (vdd_last > 0)
6123ad2737eSYing Zhang 		printf("VID: Core voltage after adjustment is at %d mV\n",
6133ad2737eSYing Zhang 		       vdd_last);
6143ad2737eSYing Zhang 	else
6153ad2737eSYing Zhang 		ret = -1;
6163ad2737eSYing Zhang exit:
6173ad2737eSYing Zhang 	if (re_enable)
6183ad2737eSYing Zhang 		enable_interrupts();
6191be8d10bSWenbin Song 
6201be8d10bSWenbin Song 	i2c_multiplexer_select_vid_channel(I2C_MUX_CH_DEFAULT);
6211be8d10bSWenbin Song 
6223ad2737eSYing Zhang 	return ret;
6233ad2737eSYing Zhang }
62429ca713cSPriyanka Jain #endif
6253ad2737eSYing Zhang 
print_vdd(void)6263ad2737eSYing Zhang static int print_vdd(void)
6273ad2737eSYing Zhang {
6283ad2737eSYing Zhang 	int vdd_last, ret, i2caddress;
6293ad2737eSYing Zhang 
6303ad2737eSYing Zhang 	ret = i2c_multiplexer_select_vid_channel(I2C_MUX_CH_VOL_MONITOR);
6313ad2737eSYing Zhang 	if (ret) {
6323ad2737eSYing Zhang 		debug("VID : I2c failed to switch channel\n");
6333ad2737eSYing Zhang 		return -1;
6343ad2737eSYing Zhang 	}
6353ad2737eSYing Zhang 	ret = find_ir_chip_on_i2c();
6363ad2737eSYing Zhang 	if (ret < 0) {
6373ad2737eSYing Zhang 		printf("VID: Could not find voltage regulator on I2C.\n");
6381be8d10bSWenbin Song 		goto exit;
6393ad2737eSYing Zhang 	} else {
6403ad2737eSYing Zhang 		i2caddress = ret;
6413ad2737eSYing Zhang 		debug("VID: IR Chip found on I2C address 0x%02x\n", i2caddress);
6423ad2737eSYing Zhang 	}
6433ad2737eSYing Zhang 
6443ad2737eSYing Zhang 	/*
6453ad2737eSYing Zhang 	 * Read voltage monitor to check real voltage.
6463ad2737eSYing Zhang 	 */
6473ad2737eSYing Zhang 	vdd_last = read_voltage(i2caddress);
6483ad2737eSYing Zhang 	if (vdd_last < 0) {
6493ad2737eSYing Zhang 		printf("VID: Couldn't read sensor abort VID adjustment\n");
6501be8d10bSWenbin Song 		goto exit;
6513ad2737eSYing Zhang 	}
6523ad2737eSYing Zhang 	printf("VID: Core voltage is at %d mV\n", vdd_last);
6531be8d10bSWenbin Song exit:
6541be8d10bSWenbin Song 	i2c_multiplexer_select_vid_channel(I2C_MUX_CH_DEFAULT);
6553ad2737eSYing Zhang 
6561be8d10bSWenbin Song 	return ret < 0 ? -1 : 0;
6571be8d10bSWenbin Song 
6583ad2737eSYing Zhang }
6593ad2737eSYing Zhang 
do_vdd_override(cmd_tbl_t * cmdtp,int flag,int argc,char * const argv[])6603ad2737eSYing Zhang static int do_vdd_override(cmd_tbl_t *cmdtp,
6613ad2737eSYing Zhang 			   int flag, int argc,
6623ad2737eSYing Zhang 			   char * const argv[])
6633ad2737eSYing Zhang {
6643ad2737eSYing Zhang 	ulong override;
6653ad2737eSYing Zhang 
6663ad2737eSYing Zhang 	if (argc < 2)
6673ad2737eSYing Zhang 		return CMD_RET_USAGE;
6683ad2737eSYing Zhang 
6693ad2737eSYing Zhang 	if (!strict_strtoul(argv[1], 10, &override))
6703ad2737eSYing Zhang 		adjust_vdd(override);   /* the value is checked by callee */
6713ad2737eSYing Zhang 	else
6723ad2737eSYing Zhang 		return CMD_RET_USAGE;
6733ad2737eSYing Zhang 	return 0;
6743ad2737eSYing Zhang }
6753ad2737eSYing Zhang 
do_vdd_read(cmd_tbl_t * cmdtp,int flag,int argc,char * const argv[])6763ad2737eSYing Zhang static int do_vdd_read(cmd_tbl_t *cmdtp,
6773ad2737eSYing Zhang 			 int flag, int argc,
6783ad2737eSYing Zhang 			 char * const argv[])
6793ad2737eSYing Zhang {
6803ad2737eSYing Zhang 	if (argc < 1)
6813ad2737eSYing Zhang 		return CMD_RET_USAGE;
6823ad2737eSYing Zhang 	print_vdd();
6833ad2737eSYing Zhang 
6843ad2737eSYing Zhang 	return 0;
6853ad2737eSYing Zhang }
6863ad2737eSYing Zhang 
6873ad2737eSYing Zhang U_BOOT_CMD(
6883ad2737eSYing Zhang 	vdd_override, 2, 0, do_vdd_override,
6893ad2737eSYing Zhang 	"override VDD",
6903ad2737eSYing Zhang 	" - override with the voltage specified in mV, eg. 1050"
6913ad2737eSYing Zhang );
6923ad2737eSYing Zhang 
6933ad2737eSYing Zhang U_BOOT_CMD(
6943ad2737eSYing Zhang 	vdd_read, 1, 0, do_vdd_read,
6953ad2737eSYing Zhang 	"read VDD",
6963ad2737eSYing Zhang 	" - Read the voltage specified in mV"
6973ad2737eSYing Zhang )
698