18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/* cx25840 firmware functions
38c2ecf20Sopenharmony_ci */
48c2ecf20Sopenharmony_ci
58c2ecf20Sopenharmony_ci#include <linux/module.h>
68c2ecf20Sopenharmony_ci#include <linux/i2c.h>
78c2ecf20Sopenharmony_ci#include <linux/firmware.h>
88c2ecf20Sopenharmony_ci#include <media/v4l2-common.h>
98c2ecf20Sopenharmony_ci#include <media/drv-intf/cx25840.h>
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include "cx25840-core.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci/*
148c2ecf20Sopenharmony_ci * Mike Isely <isely@pobox.com> - The FWSEND parameter controls the
158c2ecf20Sopenharmony_ci * size of the firmware chunks sent down the I2C bus to the chip.
168c2ecf20Sopenharmony_ci * Previously this had been set to 1024 but unfortunately some I2C
178c2ecf20Sopenharmony_ci * implementations can't transfer data in such big gulps.
188c2ecf20Sopenharmony_ci * Specifically, the pvrusb2 driver has a hard limit of around 60
198c2ecf20Sopenharmony_ci * bytes, due to the encapsulation there of I2C traffic into USB
208c2ecf20Sopenharmony_ci * messages.  So we have to significantly reduce this parameter.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci#define FWSEND 48
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define FWDEV(x) &((x)->dev)
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic char *firmware = "";
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cimodule_param(firmware, charp, 0444);
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ciMODULE_PARM_DESC(firmware, "Firmware image to load");
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic void start_fw_load(struct i2c_client *client)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	/* DL_ADDR_LB=0 DL_ADDR_HB=0 */
358c2ecf20Sopenharmony_ci	cx25840_write(client, 0x800, 0x00);
368c2ecf20Sopenharmony_ci	cx25840_write(client, 0x801, 0x00);
378c2ecf20Sopenharmony_ci	// DL_MAP=3 DL_AUTO_INC=0 DL_ENABLE=1
388c2ecf20Sopenharmony_ci	cx25840_write(client, 0x803, 0x0b);
398c2ecf20Sopenharmony_ci	/* AUTO_INC_DIS=1 */
408c2ecf20Sopenharmony_ci	cx25840_write(client, 0x000, 0x20);
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic void end_fw_load(struct i2c_client *client)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	/* AUTO_INC_DIS=0 */
468c2ecf20Sopenharmony_ci	cx25840_write(client, 0x000, 0x00);
478c2ecf20Sopenharmony_ci	/* DL_ENABLE=0 */
488c2ecf20Sopenharmony_ci	cx25840_write(client, 0x803, 0x03);
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#define CX2388x_FIRMWARE "v4l-cx23885-avcore-01.fw"
528c2ecf20Sopenharmony_ci#define CX231xx_FIRMWARE "v4l-cx231xx-avcore-01.fw"
538c2ecf20Sopenharmony_ci#define CX25840_FIRMWARE "v4l-cx25840.fw"
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistatic const char *get_fw_name(struct i2c_client *client)
568c2ecf20Sopenharmony_ci{
578c2ecf20Sopenharmony_ci	struct cx25840_state *state = to_state(i2c_get_clientdata(client));
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	if (firmware[0])
608c2ecf20Sopenharmony_ci		return firmware;
618c2ecf20Sopenharmony_ci	if (is_cx2388x(state))
628c2ecf20Sopenharmony_ci		return CX2388x_FIRMWARE;
638c2ecf20Sopenharmony_ci	if (is_cx231xx(state))
648c2ecf20Sopenharmony_ci		return CX231xx_FIRMWARE;
658c2ecf20Sopenharmony_ci	return CX25840_FIRMWARE;
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic int check_fw_load(struct i2c_client *client, int size)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	/* DL_ADDR_HB DL_ADDR_LB */
718c2ecf20Sopenharmony_ci	int s = cx25840_read(client, 0x801) << 8;
728c2ecf20Sopenharmony_ci	s |= cx25840_read(client, 0x800);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	if (size != s) {
758c2ecf20Sopenharmony_ci		v4l_err(client, "firmware %s load failed\n",
768c2ecf20Sopenharmony_ci				get_fw_name(client));
778c2ecf20Sopenharmony_ci		return -EINVAL;
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	v4l_info(client, "loaded %s firmware (%d bytes)\n",
818c2ecf20Sopenharmony_ci			get_fw_name(client), size);
828c2ecf20Sopenharmony_ci	return 0;
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic int fw_write(struct i2c_client *client, const u8 *data, int size)
868c2ecf20Sopenharmony_ci{
878c2ecf20Sopenharmony_ci	if (i2c_master_send(client, data, size) < size) {
888c2ecf20Sopenharmony_ci		v4l_err(client, "firmware load i2c failure\n");
898c2ecf20Sopenharmony_ci		return -ENOSYS;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	return 0;
938c2ecf20Sopenharmony_ci}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ciint cx25840_loadfw(struct i2c_client *client)
968c2ecf20Sopenharmony_ci{
978c2ecf20Sopenharmony_ci	struct cx25840_state *state = to_state(i2c_get_clientdata(client));
988c2ecf20Sopenharmony_ci	const struct firmware *fw = NULL;
998c2ecf20Sopenharmony_ci	u8 buffer[FWSEND];
1008c2ecf20Sopenharmony_ci	const u8 *ptr;
1018c2ecf20Sopenharmony_ci	const char *fwname = get_fw_name(client);
1028c2ecf20Sopenharmony_ci	int size, retval;
1038c2ecf20Sopenharmony_ci	int max_buf_size = FWSEND;
1048c2ecf20Sopenharmony_ci	u32 gpio_oe = 0, gpio_da = 0;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	if (is_cx2388x(state)) {
1078c2ecf20Sopenharmony_ci		/* Preserve the GPIO OE and output bits */
1088c2ecf20Sopenharmony_ci		gpio_oe = cx25840_read(client, 0x160);
1098c2ecf20Sopenharmony_ci		gpio_da = cx25840_read(client, 0x164);
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	/* cx231xx cannot accept more than 16 bytes at a time */
1138c2ecf20Sopenharmony_ci	if (is_cx231xx(state) && max_buf_size > 16)
1148c2ecf20Sopenharmony_ci		max_buf_size = 16;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	if (request_firmware(&fw, fwname, FWDEV(client)) != 0) {
1178c2ecf20Sopenharmony_ci		v4l_err(client, "unable to open firmware %s\n", fwname);
1188c2ecf20Sopenharmony_ci		return -EINVAL;
1198c2ecf20Sopenharmony_ci	}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	start_fw_load(client);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	buffer[0] = 0x08;
1248c2ecf20Sopenharmony_ci	buffer[1] = 0x02;
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	size = fw->size;
1278c2ecf20Sopenharmony_ci	ptr = fw->data;
1288c2ecf20Sopenharmony_ci	while (size > 0) {
1298c2ecf20Sopenharmony_ci		int len = min(max_buf_size - 2, size);
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		memcpy(buffer + 2, ptr, len);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		retval = fw_write(client, buffer, len + 2);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci		if (retval < 0) {
1368c2ecf20Sopenharmony_ci			release_firmware(fw);
1378c2ecf20Sopenharmony_ci			return retval;
1388c2ecf20Sopenharmony_ci		}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci		size -= len;
1418c2ecf20Sopenharmony_ci		ptr += len;
1428c2ecf20Sopenharmony_ci	}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	end_fw_load(client);
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	size = fw->size;
1478c2ecf20Sopenharmony_ci	release_firmware(fw);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	if (is_cx2388x(state)) {
1508c2ecf20Sopenharmony_ci		/* Restore GPIO configuration after f/w load */
1518c2ecf20Sopenharmony_ci		cx25840_write(client, 0x160, gpio_oe);
1528c2ecf20Sopenharmony_ci		cx25840_write(client, 0x164, gpio_da);
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	return check_fw_load(client, size);
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ciMODULE_FIRMWARE(CX2388x_FIRMWARE);
1598c2ecf20Sopenharmony_ciMODULE_FIRMWARE(CX231xx_FIRMWARE);
1608c2ecf20Sopenharmony_ciMODULE_FIRMWARE(CX25840_FIRMWARE);
1618c2ecf20Sopenharmony_ci
162