brintos

brintos / linux-shallow public Read only

0
0
Text · 15.3 KiB · 8ef010a Raw
577 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Driver for the s5k83a sensor4 *5 * Copyright (C) 2008 Erik Andrén6 * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.7 * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>8 *9 * Portions of code to USB interface and ALi driver software,10 * Copyright (c) 2006 Willem Duinker11 * v4l2 interface modeled after the V4L2 driver12 * for SN9C10x PC Camera Controllers13 */14 15#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt16 17#include <linux/kthread.h>18#include "m5602_s5k83a.h"19 20static int s5k83a_s_ctrl(struct v4l2_ctrl *ctrl);21 22static const struct v4l2_ctrl_ops s5k83a_ctrl_ops = {23	.s_ctrl = s5k83a_s_ctrl,24};25 26static struct v4l2_pix_format s5k83a_modes[] = {27	{28		640,29		480,30		V4L2_PIX_FMT_SBGGR8,31		V4L2_FIELD_NONE,32		.sizeimage =33			640 * 480,34		.bytesperline = 640,35		.colorspace = V4L2_COLORSPACE_SRGB,36		.priv = 037	}38};39 40static const unsigned char preinit_s5k83a[][4] = {41	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},42	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},43	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},44	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},45	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},46	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00},47	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00},48 49	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},50	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},51	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},52	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00},53	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00},54	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},55	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},56	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},57	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},58	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00},59	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00},60	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},61	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},62	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},63	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},64	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},65	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},66	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},67	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00},68	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},69	{BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00},70	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},71	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00},72	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},73	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},74	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00},75};76 77/* This could probably be considerably shortened.78   I don't have the hardware to experiment with it, patches welcome79*/80static const unsigned char init_s5k83a[][4] = {81	/* The following sequence is useless after a clean boot82	   but is necessary after resume from suspend */83	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},84	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},85	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00},86	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00},87	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},88	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},89	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},90	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},91	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00},92	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00},93	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},94	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},95	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},96	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},97	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},98	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},99	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},100	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00},101	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},102	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},103	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},104	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00},105	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},106	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},107	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00},108 109	{SENSOR, S5K83A_PAGE_MAP, 0x04, 0x00},110	{SENSOR, 0xaf, 0x01, 0x00},111	{SENSOR, S5K83A_PAGE_MAP, 0x00, 0x00},112	{SENSOR, 0x7b, 0xff, 0x00},113	{SENSOR, S5K83A_PAGE_MAP, 0x05, 0x00},114	{SENSOR, 0x01, 0x50, 0x00},115	{SENSOR, 0x12, 0x20, 0x00},116	{SENSOR, 0x17, 0x40, 0x00},117	{SENSOR, 0x1c, 0x00, 0x00},118	{SENSOR, 0x02, 0x70, 0x00},119	{SENSOR, 0x03, 0x0b, 0x00},120	{SENSOR, 0x04, 0xf0, 0x00},121	{SENSOR, 0x05, 0x0b, 0x00},122	{SENSOR, 0x06, 0x71, 0x00},123	{SENSOR, 0x07, 0xe8, 0x00}, /* 488 */124	{SENSOR, 0x08, 0x02, 0x00},125	{SENSOR, 0x09, 0x88, 0x00}, /* 648 */126	{SENSOR, 0x14, 0x00, 0x00},127	{SENSOR, 0x15, 0x20, 0x00}, /* 32 */128	{SENSOR, 0x19, 0x00, 0x00},129	{SENSOR, 0x1a, 0x98, 0x00}, /* 152 */130	{SENSOR, 0x0f, 0x02, 0x00},131	{SENSOR, 0x10, 0xe5, 0x00}, /* 741 */132	/* normal colors133	(this is value after boot, but after tries can be different) */134	{SENSOR, 0x00, 0x06, 0x00},135};136 137static const unsigned char start_s5k83a[][4] = {138	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00},139	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},140	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},141	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},142	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00},143	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00},144	{BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00},145	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},146	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},147	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},148	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},149	{BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00},150	{BRIDGE, M5602_XB_VSYNC_PARA, 0xe4, 0x00}, /* 484 */151	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},152	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},153	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},154	{BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00},155	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},156	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},157	{BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00},158	{BRIDGE, M5602_XB_HSYNC_PARA, 0x7f, 0x00}, /* 639 */159	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},160	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},161	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},162};163 164static void s5k83a_dump_registers(struct sd *sd);165static int s5k83a_get_rotation(struct sd *sd, u8 *reg_data);166static int s5k83a_set_led_indication(struct sd *sd, u8 val);167static int s5k83a_set_flip_real(struct gspca_dev *gspca_dev,168				__s32 vflip, __s32 hflip);169 170int s5k83a_probe(struct sd *sd)171{172	u8 prod_id = 0, ver_id = 0;173	int i, err = 0;174	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;175 176	if (force_sensor) {177		if (force_sensor == S5K83A_SENSOR) {178			pr_info("Forcing a %s sensor\n", s5k83a.name);179			goto sensor_found;180		}181		/* If we want to force another sensor, don't try to probe this182		 * one */183		return -ENODEV;184	}185 186	gspca_dbg(gspca_dev, D_PROBE, "Probing for a s5k83a sensor\n");187 188	/* Preinit the sensor */189	for (i = 0; i < ARRAY_SIZE(preinit_s5k83a) && !err; i++) {190		u8 data[2] = {preinit_s5k83a[i][2], preinit_s5k83a[i][3]};191		if (preinit_s5k83a[i][0] == SENSOR)192			err = m5602_write_sensor(sd, preinit_s5k83a[i][1],193				data, 2);194		else195			err = m5602_write_bridge(sd, preinit_s5k83a[i][1],196				data[0]);197	}198 199	/* We don't know what register (if any) that contain the product id200	 * Just pick the first addresses that seem to produce the same results201	 * on multiple machines */202	if (m5602_read_sensor(sd, 0x00, &prod_id, 1))203		return -ENODEV;204 205	if (m5602_read_sensor(sd, 0x01, &ver_id, 1))206		return -ENODEV;207 208	if ((prod_id == 0xff) || (ver_id == 0xff))209		return -ENODEV;210	else211		pr_info("Detected a s5k83a sensor\n");212 213sensor_found:214	sd->gspca_dev.cam.cam_mode = s5k83a_modes;215	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(s5k83a_modes);216 217	/* null the pointer! thread is't running now */218	sd->rotation_thread = NULL;219 220	return 0;221}222 223int s5k83a_init(struct sd *sd)224{225	int i, err = 0;226 227	for (i = 0; i < ARRAY_SIZE(init_s5k83a) && !err; i++) {228		u8 data[2] = {0x00, 0x00};229 230		switch (init_s5k83a[i][0]) {231		case BRIDGE:232			err = m5602_write_bridge(sd,233					init_s5k83a[i][1],234					init_s5k83a[i][2]);235			break;236 237		case SENSOR:238			data[0] = init_s5k83a[i][2];239			err = m5602_write_sensor(sd,240				init_s5k83a[i][1], data, 1);241			break;242 243		case SENSOR_LONG:244			data[0] = init_s5k83a[i][2];245			data[1] = init_s5k83a[i][3];246			err = m5602_write_sensor(sd,247				init_s5k83a[i][1], data, 2);248			break;249		default:250			pr_info("Invalid stream command, exiting init\n");251			return -EINVAL;252		}253	}254 255	if (dump_sensor)256		s5k83a_dump_registers(sd);257 258	return err;259}260 261int s5k83a_init_controls(struct sd *sd)262{263	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;264 265	sd->gspca_dev.vdev.ctrl_handler = hdl;266	v4l2_ctrl_handler_init(hdl, 6);267 268	v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_BRIGHTNESS,269			  0, 255, 1, S5K83A_DEFAULT_BRIGHTNESS);270 271	v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_EXPOSURE,272			  0, S5K83A_MAXIMUM_EXPOSURE, 1,273			  S5K83A_DEFAULT_EXPOSURE);274 275	v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_GAIN,276			  0, 255, 1, S5K83A_DEFAULT_GAIN);277 278	sd->hflip = v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_HFLIP,279				      0, 1, 1, 0);280	sd->vflip = v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_VFLIP,281				      0, 1, 1, 0);282 283	if (hdl->error) {284		pr_err("Could not initialize controls\n");285		return hdl->error;286	}287 288	v4l2_ctrl_cluster(2, &sd->hflip);289 290	return 0;291}292 293static int rotation_thread_function(void *data)294{295	struct sd *sd = (struct sd *) data;296	u8 reg, previous_rotation = 0;297	__s32 vflip, hflip;298 299	set_current_state(TASK_INTERRUPTIBLE);300	while (!schedule_timeout(msecs_to_jiffies(100))) {301		if (mutex_lock_interruptible(&sd->gspca_dev.usb_lock))302			break;303 304		s5k83a_get_rotation(sd, &reg);305		if (previous_rotation != reg) {306			previous_rotation = reg;307			pr_info("Camera was flipped\n");308 309			hflip = sd->hflip->val;310			vflip = sd->vflip->val;311 312			if (reg) {313				vflip = !vflip;314				hflip = !hflip;315			}316			s5k83a_set_flip_real((struct gspca_dev *) sd,317					      vflip, hflip);318		}319 320		mutex_unlock(&sd->gspca_dev.usb_lock);321		set_current_state(TASK_INTERRUPTIBLE);322	}323 324	/* return to "front" flip */325	if (previous_rotation) {326		hflip = sd->hflip->val;327		vflip = sd->vflip->val;328		s5k83a_set_flip_real((struct gspca_dev *) sd, vflip, hflip);329	}330 331	sd->rotation_thread = NULL;332	return 0;333}334 335int s5k83a_start(struct sd *sd)336{337	int i, err = 0;338 339	/* Create another thread, polling the GPIO ports of the camera to check340	   if it got rotated. This is how the windows driver does it so we have341	   to assume that there is no better way of accomplishing this */342	sd->rotation_thread = kthread_run(rotation_thread_function,343					  sd, "rotation thread");344	if (IS_ERR(sd->rotation_thread)) {345		err = PTR_ERR(sd->rotation_thread);346		sd->rotation_thread = NULL;347		return err;348	}349 350	/* Preinit the sensor */351	for (i = 0; i < ARRAY_SIZE(start_s5k83a) && !err; i++) {352		u8 data[2] = {start_s5k83a[i][2], start_s5k83a[i][3]};353		if (start_s5k83a[i][0] == SENSOR)354			err = m5602_write_sensor(sd, start_s5k83a[i][1],355				data, 2);356		else357			err = m5602_write_bridge(sd, start_s5k83a[i][1],358				data[0]);359	}360	if (err < 0)361		return err;362 363	return s5k83a_set_led_indication(sd, 1);364}365 366int s5k83a_stop(struct sd *sd)367{368	if (sd->rotation_thread)369		kthread_stop(sd->rotation_thread);370 371	return s5k83a_set_led_indication(sd, 0);372}373 374void s5k83a_disconnect(struct sd *sd)375{376	s5k83a_stop(sd);377 378	sd->sensor = NULL;379}380 381static int s5k83a_set_gain(struct gspca_dev *gspca_dev, __s32 val)382{383	int err;384	u8 data[2];385	struct sd *sd = (struct sd *) gspca_dev;386 387	data[0] = 0x00;388	data[1] = 0x20;389	err = m5602_write_sensor(sd, 0x14, data, 2);390	if (err < 0)391		return err;392 393	data[0] = 0x01;394	data[1] = 0x00;395	err = m5602_write_sensor(sd, 0x0d, data, 2);396	if (err < 0)397		return err;398 399	/* FIXME: This is not sane, we need to figure out the composition400		  of these registers */401	data[0] = val >> 3; /* gain, high 5 bits */402	data[1] = val >> 1; /* gain, high 7 bits */403	err = m5602_write_sensor(sd, S5K83A_GAIN, data, 2);404 405	return err;406}407 408static int s5k83a_set_brightness(struct gspca_dev *gspca_dev, __s32 val)409{410	u8 data[1];411	struct sd *sd = (struct sd *) gspca_dev;412 413	data[0] = val;414	return m5602_write_sensor(sd, S5K83A_BRIGHTNESS, data, 1);415}416 417static int s5k83a_set_exposure(struct gspca_dev *gspca_dev, __s32 val)418{419	u8 data[2];420	struct sd *sd = (struct sd *) gspca_dev;421 422	data[0] = 0;423	data[1] = val;424	return m5602_write_sensor(sd, S5K83A_EXPOSURE, data, 2);425}426 427static int s5k83a_set_flip_real(struct gspca_dev *gspca_dev,428				__s32 vflip, __s32 hflip)429{430	int err;431	u8 data[1];432	struct sd *sd = (struct sd *) gspca_dev;433 434	data[0] = 0x05;435	err = m5602_write_sensor(sd, S5K83A_PAGE_MAP, data, 1);436	if (err < 0)437		return err;438 439	/* six bit is vflip, seven is hflip */440	data[0] = S5K83A_FLIP_MASK;441	data[0] = (vflip) ? data[0] | 0x40 : data[0];442	data[0] = (hflip) ? data[0] | 0x80 : data[0];443 444	err = m5602_write_sensor(sd, S5K83A_FLIP, data, 1);445	if (err < 0)446		return err;447 448	data[0] = (vflip) ? 0x0b : 0x0a;449	err = m5602_write_sensor(sd, S5K83A_VFLIP_TUNE, data, 1);450	if (err < 0)451		return err;452 453	data[0] = (hflip) ? 0x0a : 0x0b;454	err = m5602_write_sensor(sd, S5K83A_HFLIP_TUNE, data, 1);455	return err;456}457 458static int s5k83a_set_hvflip(struct gspca_dev *gspca_dev)459{460	int err;461	u8 reg;462	struct sd *sd = (struct sd *) gspca_dev;463	int hflip = sd->hflip->val;464	int vflip = sd->vflip->val;465 466	err = s5k83a_get_rotation(sd, &reg);467	if (err < 0)468		return err;469	if (reg) {470		hflip = !hflip;471		vflip = !vflip;472	}473 474	err = s5k83a_set_flip_real(gspca_dev, vflip, hflip);475	return err;476}477 478static int s5k83a_s_ctrl(struct v4l2_ctrl *ctrl)479{480	struct gspca_dev *gspca_dev =481		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);482	int err;483 484	if (!gspca_dev->streaming)485		return 0;486 487	switch (ctrl->id) {488	case V4L2_CID_BRIGHTNESS:489		err = s5k83a_set_brightness(gspca_dev, ctrl->val);490		break;491	case V4L2_CID_EXPOSURE:492		err = s5k83a_set_exposure(gspca_dev, ctrl->val);493		break;494	case V4L2_CID_GAIN:495		err = s5k83a_set_gain(gspca_dev, ctrl->val);496		break;497	case V4L2_CID_HFLIP:498		err = s5k83a_set_hvflip(gspca_dev);499		break;500	default:501		return -EINVAL;502	}503 504	return err;505}506 507static int s5k83a_set_led_indication(struct sd *sd, u8 val)508{509	int err = 0;510	u8 data[1];511 512	err = m5602_read_bridge(sd, M5602_XB_GPIO_DAT, data);513	if (err < 0)514		return err;515 516	if (val)517		data[0] = data[0] | S5K83A_GPIO_LED_MASK;518	else519		data[0] = data[0] & ~S5K83A_GPIO_LED_MASK;520 521	err = m5602_write_bridge(sd, M5602_XB_GPIO_DAT, data[0]);522 523	return err;524}525 526/* Get camera rotation on Acer notebooks */527static int s5k83a_get_rotation(struct sd *sd, u8 *reg_data)528{529	int err = m5602_read_bridge(sd, M5602_XB_GPIO_DAT, reg_data);530	*reg_data = (*reg_data & S5K83A_GPIO_ROTATION_MASK) ? 0 : 1;531	return err;532}533 534static void s5k83a_dump_registers(struct sd *sd)535{536	int address;537	u8 page, old_page;538	m5602_read_sensor(sd, S5K83A_PAGE_MAP, &old_page, 1);539 540	for (page = 0; page < 16; page++) {541		m5602_write_sensor(sd, S5K83A_PAGE_MAP, &page, 1);542		pr_info("Dumping the s5k83a register state for page 0x%x\n",543			page);544		for (address = 0; address <= 0xff; address++) {545			u8 val = 0;546			m5602_read_sensor(sd, address, &val, 1);547			pr_info("register 0x%x contains 0x%x\n", address, val);548		}549	}550	pr_info("s5k83a register state dump complete\n");551 552	for (page = 0; page < 16; page++) {553		m5602_write_sensor(sd, S5K83A_PAGE_MAP, &page, 1);554		pr_info("Probing for which registers that are read/write for page 0x%x\n",555			page);556		for (address = 0; address <= 0xff; address++) {557			u8 old_val, ctrl_val, test_val = 0xff;558 559			m5602_read_sensor(sd, address, &old_val, 1);560			m5602_write_sensor(sd, address, &test_val, 1);561			m5602_read_sensor(sd, address, &ctrl_val, 1);562 563			if (ctrl_val == test_val)564				pr_info("register 0x%x is writeable\n",565					address);566			else567				pr_info("register 0x%x is read only\n",568					address);569 570			/* Restore original val */571			m5602_write_sensor(sd, address, &old_val, 1);572		}573	}574	pr_info("Read/write register probing complete\n");575	m5602_write_sensor(sd, S5K83A_PAGE_MAP, &old_page, 1);576}577