1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * sl28 extension commands
4  *
5  * Copyright (c) 2020 Kontron Europe GmbH
6  */
7 
8 #include <common.h>
9 #include <command.h>
10 #include <i2c.h>
11 #include <linux/delay.h>
12 
13 #define CPLD_I2C_ADDR 0x4a
14 #define REG_UFM_CTRL 0x02
15 #define   UFM_CTRL_DCLK    BIT(1)
16 #define   UFM_CTRL_DIN     BIT(2)
17 #define   UFM_CTRL_PROGRAM BIT(3)
18 #define   UFM_CTRL_ERASE   BIT(4)
19 #define   UFM_CTRL_DSHIFT  BIT(5)
20 #define   UFM_CTRL_DOUT    BIT(6)
21 #define   UFM_CTRL_BUSY    BIT(7)
22 
ufm_shift_data(struct udevice * dev,u16 data_in,u16 * data_out)23 static int ufm_shift_data(struct udevice *dev, u16 data_in, u16 *data_out)
24 {
25 	int i;
26 	int ret;
27 	u16 data = 0;
28 
29 	/* latch data */
30 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, 0);
31 	if (ret < 0)
32 		return ret;
33 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
34 	if (ret < 0)
35 		return ret;
36 
37 	/* assert drshift */
38 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
39 			       UFM_CTRL_DSHIFT | UFM_CTRL_DCLK);
40 	if (ret < 0)
41 		return ret;
42 
43 	/* clock 16 data bits, reverse order */
44 	for (i = 15; i >= 0; i--) {
45 		u8 din = (data_in & (1 << i)) ? UFM_CTRL_DIN : 0;
46 
47 		ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DSHIFT
48 				| din);
49 		if (ret < 0)
50 			return ret;
51 		if (data_out) {
52 			ret = dm_i2c_reg_read(dev, REG_UFM_CTRL);
53 			if (ret < 0)
54 				return ret;
55 			if (ret & UFM_CTRL_DOUT)
56 				data |= (1 << i);
57 		}
58 		ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
59 				       UFM_CTRL_DSHIFT | UFM_CTRL_DCLK | din);
60 		if (ret < 0)
61 			return ret;
62 	}
63 
64 	/* deassert drshift */
65 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
66 	if (ret < 0)
67 		return ret;
68 
69 	if (data_out)
70 		*data_out = data;
71 
72 	return ret;
73 }
74 
ufm_erase(struct udevice * dev)75 static int ufm_erase(struct udevice *dev)
76 {
77 	int ret;
78 
79 	/* erase, tEPMX is 500ms */
80 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
81 			       UFM_CTRL_DCLK | UFM_CTRL_ERASE);
82 	if (ret < 0)
83 		return ret;
84 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
85 	if (ret < 0)
86 		return ret;
87 	mdelay(500);
88 
89 	return 0;
90 }
91 
ufm_program(struct udevice * dev)92 static int ufm_program(struct udevice *dev)
93 {
94 	int ret;
95 
96 	/* program, tPPMX is 100us */
97 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL,
98 			       UFM_CTRL_DCLK | UFM_CTRL_PROGRAM);
99 	if (ret < 0)
100 		return ret;
101 	ret = dm_i2c_reg_write(dev, REG_UFM_CTRL, UFM_CTRL_DCLK);
102 	if (ret < 0)
103 		return ret;
104 	udelay(100);
105 
106 	return 0;
107 }
108 
ufm_write(struct udevice * dev,u16 data)109 static int ufm_write(struct udevice *dev, u16 data)
110 {
111 	int ret;
112 
113 	ret = ufm_shift_data(dev, data, NULL);
114 	if (ret < 0)
115 		return ret;
116 
117 	ret = ufm_erase(dev);
118 	if (ret < 0)
119 		return ret;
120 
121 	return ufm_program(dev);
122 }
123 
ufm_read(struct udevice * dev,u16 * data)124 static int ufm_read(struct udevice *dev, u16 *data)
125 {
126 	return ufm_shift_data(dev, 0, data);
127 }
128 
do_sl28_nvm(struct cmd_tbl * cmdtp,int flag,int argc,char * const argv[])129 static int do_sl28_nvm(struct cmd_tbl *cmdtp, int flag, int argc,
130 		       char *const argv[])
131 {
132 	struct udevice *dev;
133 	u16 nvm;
134 	int ret;
135 	char *endp;
136 
137 	if (i2c_get_chip_for_busnum(0, CPLD_I2C_ADDR, 1, &dev))
138 		return CMD_RET_FAILURE;
139 
140 	if (argc > 1) {
141 		nvm = simple_strtoul(argv[1], &endp, 16);
142 		if (*endp != '\0') {
143 			printf("ERROR: argument is not a valid number\n");
144 			ret = -EINVAL;
145 			goto out;
146 		}
147 
148 		/*
149 		 * We swap all bits, because the a zero bit in hardware means the
150 		 * feature is enabled. But this is hard for the user.
151 		 */
152 		nvm ^= 0xffff;
153 
154 		ret = ufm_write(dev, nvm);
155 		if (ret)
156 			goto out;
157 		printf("New settings will be activated after the next power cycle!\n");
158 	} else {
159 		ret = ufm_read(dev, &nvm);
160 		if (ret)
161 			goto out;
162 		nvm ^= 0xffff;
163 
164 		printf("%04hx\n", nvm);
165 	}
166 
167 	return CMD_RET_SUCCESS;
168 
169 out:
170 	printf("command failed (%d)\n", ret);
171 	return CMD_RET_FAILURE;
172 }
173 
174 static char sl28_help_text[] =
175 	"nvm [<hex>] - display/set the 16 non-volatile bits\n";
176 
177 U_BOOT_CMD_WITH_SUBCMDS(sl28, "SMARC-sAL28 specific", sl28_help_text,
178 			U_BOOT_SUBCMD_MKENT(nvm, 2, 1, do_sl28_nvm));
179