18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Copyright 2003 NVIDIA, Corporation
38c2ecf20Sopenharmony_ci * Copyright 2006 Dave Airlie
48c2ecf20Sopenharmony_ci * Copyright 2007 Maarten Maathuis
58c2ecf20Sopenharmony_ci * Copyright 2007-2009 Stuart Bennett
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a
88c2ecf20Sopenharmony_ci * copy of this software and associated documentation files (the "Software"),
98c2ecf20Sopenharmony_ci * to deal in the Software without restriction, including without limitation
108c2ecf20Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense,
118c2ecf20Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the
128c2ecf20Sopenharmony_ci * Software is furnished to do so, subject to the following conditions:
138c2ecf20Sopenharmony_ci *
148c2ecf20Sopenharmony_ci * The above copyright notice and this permission notice (including the next
158c2ecf20Sopenharmony_ci * paragraph) shall be included in all copies or substantial portions of the
168c2ecf20Sopenharmony_ci * Software.
178c2ecf20Sopenharmony_ci *
188c2ecf20Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
198c2ecf20Sopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
208c2ecf20Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
218c2ecf20Sopenharmony_ci * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
228c2ecf20Sopenharmony_ci * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
238c2ecf20Sopenharmony_ci * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
248c2ecf20Sopenharmony_ci * DEALINGS IN THE SOFTWARE.
258c2ecf20Sopenharmony_ci */
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include <drm/drm_crtc_helper.h>
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#include "nouveau_drv.h"
308c2ecf20Sopenharmony_ci#include "nouveau_encoder.h"
318c2ecf20Sopenharmony_ci#include "nouveau_connector.h"
328c2ecf20Sopenharmony_ci#include "nouveau_crtc.h"
338c2ecf20Sopenharmony_ci#include "hw.h"
348c2ecf20Sopenharmony_ci#include "nvreg.h"
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#include <subdev/bios/gpio.h>
378c2ecf20Sopenharmony_ci#include <subdev/gpio.h>
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci#include <nvif/timer.h>
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ciint nv04_dac_output_offset(struct drm_encoder *encoder)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
448c2ecf20Sopenharmony_ci	int offset = 0;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	if (dcb->or & (8 | DCB_OUTPUT_C))
478c2ecf20Sopenharmony_ci		offset += 0x68;
488c2ecf20Sopenharmony_ci	if (dcb->or & (8 | DCB_OUTPUT_B))
498c2ecf20Sopenharmony_ci		offset += 0x2000;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	return offset;
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/*
558c2ecf20Sopenharmony_ci * arbitrary limit to number of sense oscillations tolerated in one sample
568c2ecf20Sopenharmony_ci * period (observed to be at least 13 in "nvidia")
578c2ecf20Sopenharmony_ci */
588c2ecf20Sopenharmony_ci#define MAX_HBLANK_OSC 20
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci/*
618c2ecf20Sopenharmony_ci * arbitrary limit to number of conflicting sample pairs to tolerate at a
628c2ecf20Sopenharmony_ci * voltage step (observed to be at least 5 in "nvidia")
638c2ecf20Sopenharmony_ci */
648c2ecf20Sopenharmony_ci#define MAX_SAMPLE_PAIRS 10
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_cistatic int sample_load_twice(struct drm_device *dev, bool sense[2])
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(dev);
698c2ecf20Sopenharmony_ci	struct nvif_object *device = &drm->client.device.object;
708c2ecf20Sopenharmony_ci	int i;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	for (i = 0; i < 2; i++) {
738c2ecf20Sopenharmony_ci		bool sense_a, sense_b, sense_b_prime;
748c2ecf20Sopenharmony_ci		int j = 0;
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci		/*
778c2ecf20Sopenharmony_ci		 * wait for bit 0 clear -- out of hblank -- (say reg value 0x4),
788c2ecf20Sopenharmony_ci		 * then wait for transition 0x4->0x5->0x4: enter hblank, leave
798c2ecf20Sopenharmony_ci		 * hblank again
808c2ecf20Sopenharmony_ci		 * use a 10ms timeout (guards against crtc being inactive, in
818c2ecf20Sopenharmony_ci		 * which case blank state would never change)
828c2ecf20Sopenharmony_ci		 */
838c2ecf20Sopenharmony_ci		if (nvif_msec(&drm->client.device, 10,
848c2ecf20Sopenharmony_ci			if (!(nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 1))
858c2ecf20Sopenharmony_ci				break;
868c2ecf20Sopenharmony_ci		) < 0)
878c2ecf20Sopenharmony_ci			return -EBUSY;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci		if (nvif_msec(&drm->client.device, 10,
908c2ecf20Sopenharmony_ci			if ( (nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 1))
918c2ecf20Sopenharmony_ci				break;
928c2ecf20Sopenharmony_ci		) < 0)
938c2ecf20Sopenharmony_ci			return -EBUSY;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci		if (nvif_msec(&drm->client.device, 10,
968c2ecf20Sopenharmony_ci			if (!(nvif_rd32(device, NV_PRMCIO_INP0__COLOR) & 1))
978c2ecf20Sopenharmony_ci				break;
988c2ecf20Sopenharmony_ci		) < 0)
998c2ecf20Sopenharmony_ci			return -EBUSY;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci		udelay(100);
1028c2ecf20Sopenharmony_ci		/* when level triggers, sense is _LO_ */
1038c2ecf20Sopenharmony_ci		sense_a = nvif_rd08(device, NV_PRMCIO_INP0) & 0x10;
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci		/* take another reading until it agrees with sense_a... */
1068c2ecf20Sopenharmony_ci		do {
1078c2ecf20Sopenharmony_ci			udelay(100);
1088c2ecf20Sopenharmony_ci			sense_b = nvif_rd08(device, NV_PRMCIO_INP0) & 0x10;
1098c2ecf20Sopenharmony_ci			if (sense_a != sense_b) {
1108c2ecf20Sopenharmony_ci				sense_b_prime =
1118c2ecf20Sopenharmony_ci					nvif_rd08(device, NV_PRMCIO_INP0) & 0x10;
1128c2ecf20Sopenharmony_ci				if (sense_b == sense_b_prime) {
1138c2ecf20Sopenharmony_ci					/* ... unless two consecutive subsequent
1148c2ecf20Sopenharmony_ci					 * samples agree; sense_a is replaced */
1158c2ecf20Sopenharmony_ci					sense_a = sense_b;
1168c2ecf20Sopenharmony_ci					/* force mis-match so we loop */
1178c2ecf20Sopenharmony_ci					sense_b = !sense_a;
1188c2ecf20Sopenharmony_ci				}
1198c2ecf20Sopenharmony_ci			}
1208c2ecf20Sopenharmony_ci		} while ((sense_a != sense_b) && ++j < MAX_HBLANK_OSC);
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci		if (j == MAX_HBLANK_OSC)
1238c2ecf20Sopenharmony_ci			/* with so much oscillation, default to sense:LO */
1248c2ecf20Sopenharmony_ci			sense[i] = false;
1258c2ecf20Sopenharmony_ci		else
1268c2ecf20Sopenharmony_ci			sense[i] = sense_a;
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return 0;
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic enum drm_connector_status nv04_dac_detect(struct drm_encoder *encoder,
1338c2ecf20Sopenharmony_ci						 struct drm_connector *connector)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
1368c2ecf20Sopenharmony_ci	struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
1378c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(dev);
1388c2ecf20Sopenharmony_ci	uint8_t saved_seq1, saved_pi, saved_rpc1, saved_cr_mode;
1398c2ecf20Sopenharmony_ci	uint8_t saved_palette0[3], saved_palette_mask;
1408c2ecf20Sopenharmony_ci	uint32_t saved_rtest_ctrl, saved_rgen_ctrl;
1418c2ecf20Sopenharmony_ci	int i;
1428c2ecf20Sopenharmony_ci	uint8_t blue;
1438c2ecf20Sopenharmony_ci	bool sense = true;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	/*
1468c2ecf20Sopenharmony_ci	 * for this detection to work, there needs to be a mode set up on the
1478c2ecf20Sopenharmony_ci	 * CRTC.  this is presumed to be the case
1488c2ecf20Sopenharmony_ci	 */
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	if (nv_two_heads(dev))
1518c2ecf20Sopenharmony_ci		/* only implemented for head A for now */
1528c2ecf20Sopenharmony_ci		NVSetOwner(dev, 0);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	saved_cr_mode = NVReadVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX);
1558c2ecf20Sopenharmony_ci	NVWriteVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX, saved_cr_mode | 0x80);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	saved_seq1 = NVReadVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX);
1588c2ecf20Sopenharmony_ci	NVWriteVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX, saved_seq1 & ~0x20);
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	saved_rtest_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL);
1618c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL,
1628c2ecf20Sopenharmony_ci		      saved_rtest_ctrl & ~NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	msleep(10);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	saved_pi = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX);
1678c2ecf20Sopenharmony_ci	NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX,
1688c2ecf20Sopenharmony_ci		       saved_pi & ~(0x80 | MASK(NV_CIO_CRE_PIXEL_FORMAT)));
1698c2ecf20Sopenharmony_ci	saved_rpc1 = NVReadVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX);
1708c2ecf20Sopenharmony_ci	NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX, saved_rpc1 & ~0xc0);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	nvif_wr08(device, NV_PRMDIO_READ_MODE_ADDRESS, 0x0);
1738c2ecf20Sopenharmony_ci	for (i = 0; i < 3; i++)
1748c2ecf20Sopenharmony_ci		saved_palette0[i] = nvif_rd08(device, NV_PRMDIO_PALETTE_DATA);
1758c2ecf20Sopenharmony_ci	saved_palette_mask = nvif_rd08(device, NV_PRMDIO_PIXEL_MASK);
1768c2ecf20Sopenharmony_ci	nvif_wr08(device, NV_PRMDIO_PIXEL_MASK, 0);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	saved_rgen_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL);
1798c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL,
1808c2ecf20Sopenharmony_ci		      (saved_rgen_ctrl & ~(NV_PRAMDAC_GENERAL_CONTROL_BPC_8BITS |
1818c2ecf20Sopenharmony_ci					   NV_PRAMDAC_GENERAL_CONTROL_TERMINATION_75OHM)) |
1828c2ecf20Sopenharmony_ci		      NV_PRAMDAC_GENERAL_CONTROL_PIXMIX_ON);
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	blue = 8;	/* start of test range */
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	do {
1878c2ecf20Sopenharmony_ci		bool sense_pair[2];
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci		nvif_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS, 0);
1908c2ecf20Sopenharmony_ci		nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, 0);
1918c2ecf20Sopenharmony_ci		nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, 0);
1928c2ecf20Sopenharmony_ci		/* testing blue won't find monochrome monitors.  I don't care */
1938c2ecf20Sopenharmony_ci		nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, blue);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci		i = 0;
1968c2ecf20Sopenharmony_ci		/* take sample pairs until both samples in the pair agree */
1978c2ecf20Sopenharmony_ci		do {
1988c2ecf20Sopenharmony_ci			if (sample_load_twice(dev, sense_pair))
1998c2ecf20Sopenharmony_ci				goto out;
2008c2ecf20Sopenharmony_ci		} while ((sense_pair[0] != sense_pair[1]) &&
2018c2ecf20Sopenharmony_ci							++i < MAX_SAMPLE_PAIRS);
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci		if (i == MAX_SAMPLE_PAIRS)
2048c2ecf20Sopenharmony_ci			/* too much oscillation defaults to LO */
2058c2ecf20Sopenharmony_ci			sense = false;
2068c2ecf20Sopenharmony_ci		else
2078c2ecf20Sopenharmony_ci			sense = sense_pair[0];
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	/*
2108c2ecf20Sopenharmony_ci	 * if sense goes LO before blue ramps to 0x18, monitor is not connected.
2118c2ecf20Sopenharmony_ci	 * ergo, if blue gets to 0x18, monitor must be connected
2128c2ecf20Sopenharmony_ci	 */
2138c2ecf20Sopenharmony_ci	} while (++blue < 0x18 && sense);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ciout:
2168c2ecf20Sopenharmony_ci	nvif_wr08(device, NV_PRMDIO_PIXEL_MASK, saved_palette_mask);
2178c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_GENERAL_CONTROL, saved_rgen_ctrl);
2188c2ecf20Sopenharmony_ci	nvif_wr08(device, NV_PRMDIO_WRITE_MODE_ADDRESS, 0);
2198c2ecf20Sopenharmony_ci	for (i = 0; i < 3; i++)
2208c2ecf20Sopenharmony_ci		nvif_wr08(device, NV_PRMDIO_PALETTE_DATA, saved_palette0[i]);
2218c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL, saved_rtest_ctrl);
2228c2ecf20Sopenharmony_ci	NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_PIXEL_INDEX, saved_pi);
2238c2ecf20Sopenharmony_ci	NVWriteVgaCrtc(dev, 0, NV_CIO_CRE_RPC1_INDEX, saved_rpc1);
2248c2ecf20Sopenharmony_ci	NVWriteVgaSeq(dev, 0, NV_VIO_SR_CLOCK_INDEX, saved_seq1);
2258c2ecf20Sopenharmony_ci	NVWriteVgaCrtc(dev, 0, NV_CIO_CR_MODE_INDEX, saved_cr_mode);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	if (blue == 0x18) {
2288c2ecf20Sopenharmony_ci		NV_DEBUG(drm, "Load detected on head A\n");
2298c2ecf20Sopenharmony_ci		return connector_status_connected;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	return connector_status_disconnected;
2338c2ecf20Sopenharmony_ci}
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ciuint32_t nv17_dac_sample_load(struct drm_encoder *encoder)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
2388c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(dev);
2398c2ecf20Sopenharmony_ci	struct nvif_object *device = &nouveau_drm(dev)->client.device.object;
2408c2ecf20Sopenharmony_ci	struct nvkm_gpio *gpio = nvxx_gpio(&drm->client.device);
2418c2ecf20Sopenharmony_ci	struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
2428c2ecf20Sopenharmony_ci	uint32_t sample, testval, regoffset = nv04_dac_output_offset(encoder);
2438c2ecf20Sopenharmony_ci	uint32_t saved_powerctrl_2 = 0, saved_powerctrl_4 = 0, saved_routput,
2448c2ecf20Sopenharmony_ci		saved_rtest_ctrl, saved_gpio0 = 0, saved_gpio1 = 0, temp, routput;
2458c2ecf20Sopenharmony_ci	int head;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci#define RGB_TEST_DATA(r, g, b) (r << 0 | g << 10 | b << 20)
2488c2ecf20Sopenharmony_ci	if (dcb->type == DCB_OUTPUT_TV) {
2498c2ecf20Sopenharmony_ci		testval = RGB_TEST_DATA(0xa0, 0xa0, 0xa0);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci		if (drm->vbios.tvdactestval)
2528c2ecf20Sopenharmony_ci			testval = drm->vbios.tvdactestval;
2538c2ecf20Sopenharmony_ci	} else {
2548c2ecf20Sopenharmony_ci		testval = RGB_TEST_DATA(0x140, 0x140, 0x140); /* 0x94050140 */
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci		if (drm->vbios.dactestval)
2578c2ecf20Sopenharmony_ci			testval = drm->vbios.dactestval;
2588c2ecf20Sopenharmony_ci	}
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci	saved_rtest_ctrl = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
2618c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset,
2628c2ecf20Sopenharmony_ci		      saved_rtest_ctrl & ~NV_PRAMDAC_TEST_CONTROL_PWRDWN_DAC_OFF);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	saved_powerctrl_2 = nvif_rd32(device, NV_PBUS_POWERCTRL_2);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	nvif_wr32(device, NV_PBUS_POWERCTRL_2, saved_powerctrl_2 & 0xd7ffffff);
2678c2ecf20Sopenharmony_ci	if (regoffset == 0x68) {
2688c2ecf20Sopenharmony_ci		saved_powerctrl_4 = nvif_rd32(device, NV_PBUS_POWERCTRL_4);
2698c2ecf20Sopenharmony_ci		nvif_wr32(device, NV_PBUS_POWERCTRL_4, saved_powerctrl_4 & 0xffffffcf);
2708c2ecf20Sopenharmony_ci	}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	if (gpio) {
2738c2ecf20Sopenharmony_ci		saved_gpio1 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC1, 0xff);
2748c2ecf20Sopenharmony_ci		saved_gpio0 = nvkm_gpio_get(gpio, 0, DCB_GPIO_TVDAC0, 0xff);
2758c2ecf20Sopenharmony_ci		nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, dcb->type == DCB_OUTPUT_TV);
2768c2ecf20Sopenharmony_ci		nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, dcb->type == DCB_OUTPUT_TV);
2778c2ecf20Sopenharmony_ci	}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	msleep(4);
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	saved_routput = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset);
2828c2ecf20Sopenharmony_ci	head = (saved_routput & 0x100) >> 8;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	/* if there's a spare crtc, using it will minimise flicker */
2858c2ecf20Sopenharmony_ci	if (!(NVReadVgaCrtc(dev, head, NV_CIO_CRE_RPC1_INDEX) & 0xC0))
2868c2ecf20Sopenharmony_ci		head ^= 1;
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	/* nv driver and nv31 use 0xfffffeee, nv34 and 6600 use 0xfffffece */
2898c2ecf20Sopenharmony_ci	routput = (saved_routput & 0xfffffece) | head << 8;
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci	if (drm->client.device.info.family >= NV_DEVICE_INFO_V0_CURIE) {
2928c2ecf20Sopenharmony_ci		if (dcb->type == DCB_OUTPUT_TV)
2938c2ecf20Sopenharmony_ci			routput |= 0x1a << 16;
2948c2ecf20Sopenharmony_ci		else
2958c2ecf20Sopenharmony_ci			routput &= ~(0x1a << 16);
2968c2ecf20Sopenharmony_ci	}
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, routput);
2998c2ecf20Sopenharmony_ci	msleep(1);
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	temp = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset);
3028c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, temp | 1);
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, head, NV_PRAMDAC_TESTPOINT_DATA,
3058c2ecf20Sopenharmony_ci		      NV_PRAMDAC_TESTPOINT_DATA_NOTBLANK | testval);
3068c2ecf20Sopenharmony_ci	temp = NVReadRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL);
3078c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL,
3088c2ecf20Sopenharmony_ci		      temp | NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED);
3098c2ecf20Sopenharmony_ci	msleep(5);
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	sample = NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
3128c2ecf20Sopenharmony_ci	/* do it again just in case it's a residual current */
3138c2ecf20Sopenharmony_ci	sample &= NVReadRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset);
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	temp = NVReadRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL);
3168c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, head, NV_PRAMDAC_TEST_CONTROL,
3178c2ecf20Sopenharmony_ci		      temp & ~NV_PRAMDAC_TEST_CONTROL_TP_INS_EN_ASSERTED);
3188c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, head, NV_PRAMDAC_TESTPOINT_DATA, 0);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	/* bios does something more complex for restoring, but I think this is good enough */
3218c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + regoffset, saved_routput);
3228c2ecf20Sopenharmony_ci	NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + regoffset, saved_rtest_ctrl);
3238c2ecf20Sopenharmony_ci	if (regoffset == 0x68)
3248c2ecf20Sopenharmony_ci		nvif_wr32(device, NV_PBUS_POWERCTRL_4, saved_powerctrl_4);
3258c2ecf20Sopenharmony_ci	nvif_wr32(device, NV_PBUS_POWERCTRL_2, saved_powerctrl_2);
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	if (gpio) {
3288c2ecf20Sopenharmony_ci		nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC1, 0xff, saved_gpio1);
3298c2ecf20Sopenharmony_ci		nvkm_gpio_set(gpio, 0, DCB_GPIO_TVDAC0, 0xff, saved_gpio0);
3308c2ecf20Sopenharmony_ci	}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	return sample;
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic enum drm_connector_status
3368c2ecf20Sopenharmony_cinv17_dac_detect(struct drm_encoder *encoder, struct drm_connector *connector)
3378c2ecf20Sopenharmony_ci{
3388c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(encoder->dev);
3398c2ecf20Sopenharmony_ci	struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
3408c2ecf20Sopenharmony_ci
3418c2ecf20Sopenharmony_ci	if (nv04_dac_in_use(encoder))
3428c2ecf20Sopenharmony_ci		return connector_status_disconnected;
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	if (nv17_dac_sample_load(encoder) &
3458c2ecf20Sopenharmony_ci	    NV_PRAMDAC_TEST_CONTROL_SENSEB_ALLHI) {
3468c2ecf20Sopenharmony_ci		NV_DEBUG(drm, "Load detected on output %c\n",
3478c2ecf20Sopenharmony_ci			 '@' + ffs(dcb->or));
3488c2ecf20Sopenharmony_ci		return connector_status_connected;
3498c2ecf20Sopenharmony_ci	} else {
3508c2ecf20Sopenharmony_ci		return connector_status_disconnected;
3518c2ecf20Sopenharmony_ci	}
3528c2ecf20Sopenharmony_ci}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_cistatic bool nv04_dac_mode_fixup(struct drm_encoder *encoder,
3558c2ecf20Sopenharmony_ci				const struct drm_display_mode *mode,
3568c2ecf20Sopenharmony_ci				struct drm_display_mode *adjusted_mode)
3578c2ecf20Sopenharmony_ci{
3588c2ecf20Sopenharmony_ci	if (nv04_dac_in_use(encoder))
3598c2ecf20Sopenharmony_ci		return false;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	return true;
3628c2ecf20Sopenharmony_ci}
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_cistatic void nv04_dac_prepare(struct drm_encoder *encoder)
3658c2ecf20Sopenharmony_ci{
3668c2ecf20Sopenharmony_ci	const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
3678c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
3688c2ecf20Sopenharmony_ci	int head = nouveau_crtc(encoder->crtc)->index;
3698c2ecf20Sopenharmony_ci
3708c2ecf20Sopenharmony_ci	helper->dpms(encoder, DRM_MODE_DPMS_OFF);
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	nv04_dfp_disable(dev, head);
3738c2ecf20Sopenharmony_ci}
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_cistatic void nv04_dac_mode_set(struct drm_encoder *encoder,
3768c2ecf20Sopenharmony_ci			      struct drm_display_mode *mode,
3778c2ecf20Sopenharmony_ci			      struct drm_display_mode *adjusted_mode)
3788c2ecf20Sopenharmony_ci{
3798c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
3808c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(dev);
3818c2ecf20Sopenharmony_ci	int head = nouveau_crtc(encoder->crtc)->index;
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	if (nv_gf4_disp_arch(dev)) {
3848c2ecf20Sopenharmony_ci		struct drm_encoder *rebind;
3858c2ecf20Sopenharmony_ci		uint32_t dac_offset = nv04_dac_output_offset(encoder);
3868c2ecf20Sopenharmony_ci		uint32_t otherdac;
3878c2ecf20Sopenharmony_ci
3888c2ecf20Sopenharmony_ci		/* bit 16-19 are bits that are set on some G70 cards,
3898c2ecf20Sopenharmony_ci		 * but don't seem to have much effect */
3908c2ecf20Sopenharmony_ci		NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset,
3918c2ecf20Sopenharmony_ci			      head << 8 | NV_PRAMDAC_DACCLK_SEL_DACCLK);
3928c2ecf20Sopenharmony_ci		/* force any other vga encoders to bind to the other crtc */
3938c2ecf20Sopenharmony_ci		list_for_each_entry(rebind, &dev->mode_config.encoder_list, head) {
3948c2ecf20Sopenharmony_ci			if (rebind == encoder
3958c2ecf20Sopenharmony_ci			    || nouveau_encoder(rebind)->dcb->type != DCB_OUTPUT_ANALOG)
3968c2ecf20Sopenharmony_ci				continue;
3978c2ecf20Sopenharmony_ci
3988c2ecf20Sopenharmony_ci			dac_offset = nv04_dac_output_offset(rebind);
3998c2ecf20Sopenharmony_ci			otherdac = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset);
4008c2ecf20Sopenharmony_ci			NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + dac_offset,
4018c2ecf20Sopenharmony_ci				      (otherdac & ~0x0100) | (head ^ 1) << 8);
4028c2ecf20Sopenharmony_ci		}
4038c2ecf20Sopenharmony_ci	}
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	/* This could use refinement for flatpanels, but it should work this way */
4068c2ecf20Sopenharmony_ci	if (drm->client.device.info.chipset < 0x44)
4078c2ecf20Sopenharmony_ci		NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0xf0000000);
4088c2ecf20Sopenharmony_ci	else
4098c2ecf20Sopenharmony_ci		NVWriteRAMDAC(dev, 0, NV_PRAMDAC_TEST_CONTROL + nv04_dac_output_offset(encoder), 0x00100000);
4108c2ecf20Sopenharmony_ci}
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_cistatic void nv04_dac_commit(struct drm_encoder *encoder)
4138c2ecf20Sopenharmony_ci{
4148c2ecf20Sopenharmony_ci	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
4158c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(encoder->dev);
4168c2ecf20Sopenharmony_ci	struct nouveau_crtc *nv_crtc = nouveau_crtc(encoder->crtc);
4178c2ecf20Sopenharmony_ci	const struct drm_encoder_helper_funcs *helper = encoder->helper_private;
4188c2ecf20Sopenharmony_ci
4198c2ecf20Sopenharmony_ci	helper->dpms(encoder, DRM_MODE_DPMS_ON);
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	NV_DEBUG(drm, "Output %s is running on CRTC %d using output %c\n",
4228c2ecf20Sopenharmony_ci		 nv04_encoder_get_connector(nv_encoder)->base.name,
4238c2ecf20Sopenharmony_ci		 nv_crtc->index, '@' + ffs(nv_encoder->dcb->or));
4248c2ecf20Sopenharmony_ci}
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_civoid nv04_dac_update_dacclk(struct drm_encoder *encoder, bool enable)
4278c2ecf20Sopenharmony_ci{
4288c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4298c2ecf20Sopenharmony_ci	struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	if (nv_gf4_disp_arch(dev)) {
4328c2ecf20Sopenharmony_ci		uint32_t *dac_users = &nv04_display(dev)->dac_users[ffs(dcb->or) - 1];
4338c2ecf20Sopenharmony_ci		int dacclk_off = NV_PRAMDAC_DACCLK + nv04_dac_output_offset(encoder);
4348c2ecf20Sopenharmony_ci		uint32_t dacclk = NVReadRAMDAC(dev, 0, dacclk_off);
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_ci		if (enable) {
4378c2ecf20Sopenharmony_ci			*dac_users |= 1 << dcb->index;
4388c2ecf20Sopenharmony_ci			NVWriteRAMDAC(dev, 0, dacclk_off, dacclk | NV_PRAMDAC_DACCLK_SEL_DACCLK);
4398c2ecf20Sopenharmony_ci
4408c2ecf20Sopenharmony_ci		} else {
4418c2ecf20Sopenharmony_ci			*dac_users &= ~(1 << dcb->index);
4428c2ecf20Sopenharmony_ci			if (!*dac_users)
4438c2ecf20Sopenharmony_ci				NVWriteRAMDAC(dev, 0, dacclk_off,
4448c2ecf20Sopenharmony_ci					dacclk & ~NV_PRAMDAC_DACCLK_SEL_DACCLK);
4458c2ecf20Sopenharmony_ci		}
4468c2ecf20Sopenharmony_ci	}
4478c2ecf20Sopenharmony_ci}
4488c2ecf20Sopenharmony_ci
4498c2ecf20Sopenharmony_ci/* Check if the DAC corresponding to 'encoder' is being used by
4508c2ecf20Sopenharmony_ci * someone else. */
4518c2ecf20Sopenharmony_cibool nv04_dac_in_use(struct drm_encoder *encoder)
4528c2ecf20Sopenharmony_ci{
4538c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4548c2ecf20Sopenharmony_ci	struct dcb_output *dcb = nouveau_encoder(encoder)->dcb;
4558c2ecf20Sopenharmony_ci
4568c2ecf20Sopenharmony_ci	return nv_gf4_disp_arch(encoder->dev) &&
4578c2ecf20Sopenharmony_ci		(nv04_display(dev)->dac_users[ffs(dcb->or) - 1] & ~(1 << dcb->index));
4588c2ecf20Sopenharmony_ci}
4598c2ecf20Sopenharmony_ci
4608c2ecf20Sopenharmony_cistatic void nv04_dac_dpms(struct drm_encoder *encoder, int mode)
4618c2ecf20Sopenharmony_ci{
4628c2ecf20Sopenharmony_ci	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
4638c2ecf20Sopenharmony_ci	struct nouveau_drm *drm = nouveau_drm(encoder->dev);
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci	if (nv_encoder->last_dpms == mode)
4668c2ecf20Sopenharmony_ci		return;
4678c2ecf20Sopenharmony_ci	nv_encoder->last_dpms = mode;
4688c2ecf20Sopenharmony_ci
4698c2ecf20Sopenharmony_ci	NV_DEBUG(drm, "Setting dpms mode %d on vga encoder (output %d)\n",
4708c2ecf20Sopenharmony_ci		 mode, nv_encoder->dcb->index);
4718c2ecf20Sopenharmony_ci
4728c2ecf20Sopenharmony_ci	nv04_dac_update_dacclk(encoder, mode == DRM_MODE_DPMS_ON);
4738c2ecf20Sopenharmony_ci}
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_cistatic void nv04_dac_save(struct drm_encoder *encoder)
4768c2ecf20Sopenharmony_ci{
4778c2ecf20Sopenharmony_ci	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
4788c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4798c2ecf20Sopenharmony_ci
4808c2ecf20Sopenharmony_ci	if (nv_gf4_disp_arch(dev))
4818c2ecf20Sopenharmony_ci		nv_encoder->restore.output = NVReadRAMDAC(dev, 0, NV_PRAMDAC_DACCLK +
4828c2ecf20Sopenharmony_ci							  nv04_dac_output_offset(encoder));
4838c2ecf20Sopenharmony_ci}
4848c2ecf20Sopenharmony_ci
4858c2ecf20Sopenharmony_cistatic void nv04_dac_restore(struct drm_encoder *encoder)
4868c2ecf20Sopenharmony_ci{
4878c2ecf20Sopenharmony_ci	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
4888c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	if (nv_gf4_disp_arch(dev))
4918c2ecf20Sopenharmony_ci		NVWriteRAMDAC(dev, 0, NV_PRAMDAC_DACCLK + nv04_dac_output_offset(encoder),
4928c2ecf20Sopenharmony_ci			      nv_encoder->restore.output);
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci	nv_encoder->last_dpms = NV_DPMS_CLEARED;
4958c2ecf20Sopenharmony_ci}
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_cistatic void nv04_dac_destroy(struct drm_encoder *encoder)
4988c2ecf20Sopenharmony_ci{
4998c2ecf20Sopenharmony_ci	struct nouveau_encoder *nv_encoder = nouveau_encoder(encoder);
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_ci	drm_encoder_cleanup(encoder);
5028c2ecf20Sopenharmony_ci	kfree(nv_encoder);
5038c2ecf20Sopenharmony_ci}
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_cistatic const struct drm_encoder_helper_funcs nv04_dac_helper_funcs = {
5068c2ecf20Sopenharmony_ci	.dpms = nv04_dac_dpms,
5078c2ecf20Sopenharmony_ci	.mode_fixup = nv04_dac_mode_fixup,
5088c2ecf20Sopenharmony_ci	.prepare = nv04_dac_prepare,
5098c2ecf20Sopenharmony_ci	.commit = nv04_dac_commit,
5108c2ecf20Sopenharmony_ci	.mode_set = nv04_dac_mode_set,
5118c2ecf20Sopenharmony_ci	.detect = nv04_dac_detect
5128c2ecf20Sopenharmony_ci};
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_cistatic const struct drm_encoder_helper_funcs nv17_dac_helper_funcs = {
5158c2ecf20Sopenharmony_ci	.dpms = nv04_dac_dpms,
5168c2ecf20Sopenharmony_ci	.mode_fixup = nv04_dac_mode_fixup,
5178c2ecf20Sopenharmony_ci	.prepare = nv04_dac_prepare,
5188c2ecf20Sopenharmony_ci	.commit = nv04_dac_commit,
5198c2ecf20Sopenharmony_ci	.mode_set = nv04_dac_mode_set,
5208c2ecf20Sopenharmony_ci	.detect = nv17_dac_detect
5218c2ecf20Sopenharmony_ci};
5228c2ecf20Sopenharmony_ci
5238c2ecf20Sopenharmony_cistatic const struct drm_encoder_funcs nv04_dac_funcs = {
5248c2ecf20Sopenharmony_ci	.destroy = nv04_dac_destroy,
5258c2ecf20Sopenharmony_ci};
5268c2ecf20Sopenharmony_ci
5278c2ecf20Sopenharmony_ciint
5288c2ecf20Sopenharmony_cinv04_dac_create(struct drm_connector *connector, struct dcb_output *entry)
5298c2ecf20Sopenharmony_ci{
5308c2ecf20Sopenharmony_ci	const struct drm_encoder_helper_funcs *helper;
5318c2ecf20Sopenharmony_ci	struct nouveau_encoder *nv_encoder = NULL;
5328c2ecf20Sopenharmony_ci	struct drm_device *dev = connector->dev;
5338c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
5348c2ecf20Sopenharmony_ci
5358c2ecf20Sopenharmony_ci	nv_encoder = kzalloc(sizeof(*nv_encoder), GFP_KERNEL);
5368c2ecf20Sopenharmony_ci	if (!nv_encoder)
5378c2ecf20Sopenharmony_ci		return -ENOMEM;
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	encoder = to_drm_encoder(nv_encoder);
5408c2ecf20Sopenharmony_ci
5418c2ecf20Sopenharmony_ci	nv_encoder->dcb = entry;
5428c2ecf20Sopenharmony_ci	nv_encoder->or = ffs(entry->or) - 1;
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	nv_encoder->enc_save = nv04_dac_save;
5458c2ecf20Sopenharmony_ci	nv_encoder->enc_restore = nv04_dac_restore;
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ci	if (nv_gf4_disp_arch(dev))
5488c2ecf20Sopenharmony_ci		helper = &nv17_dac_helper_funcs;
5498c2ecf20Sopenharmony_ci	else
5508c2ecf20Sopenharmony_ci		helper = &nv04_dac_helper_funcs;
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci	drm_encoder_init(dev, encoder, &nv04_dac_funcs, DRM_MODE_ENCODER_DAC,
5538c2ecf20Sopenharmony_ci			 NULL);
5548c2ecf20Sopenharmony_ci	drm_encoder_helper_add(encoder, helper);
5558c2ecf20Sopenharmony_ci
5568c2ecf20Sopenharmony_ci	encoder->possible_crtcs = entry->heads;
5578c2ecf20Sopenharmony_ci	encoder->possible_clones = 0;
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_ci	drm_connector_attach_encoder(connector, encoder);
5608c2ecf20Sopenharmony_ci	return 0;
5618c2ecf20Sopenharmony_ci}
562