1*2e6662efStnn /* $NetBSD: ssdfb_i2c.c,v 1.11 2021/08/05 22:31:20 tnn Exp $ */
205437641Stnn
305437641Stnn /*
405437641Stnn * Copyright (c) 2019 The NetBSD Foundation, Inc.
505437641Stnn * All rights reserved.
605437641Stnn *
705437641Stnn * This code is derived from software contributed to The NetBSD Foundation
805437641Stnn * by Tobias Nygren.
905437641Stnn *
1005437641Stnn * Redistribution and use in source and binary forms, with or without
1105437641Stnn * modification, are permitted provided that the following conditions
1205437641Stnn * are met:
1305437641Stnn * 1. Redistributions of source code must retain the above copyright
1405437641Stnn * notice, this list of conditions and the following disclaimer.
1505437641Stnn * 2. Redistributions in binary form must reproduce the above copyright
1605437641Stnn * notice, this list of conditions and the following disclaimer in the
1705437641Stnn * documentation and/or other materials provided with the distribution.
1805437641Stnn *
1905437641Stnn * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
2005437641Stnn * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
2105437641Stnn * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
2205437641Stnn * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
2305437641Stnn * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
2405437641Stnn * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
2505437641Stnn * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
2605437641Stnn * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
2705437641Stnn * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2805437641Stnn * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2905437641Stnn * POSSIBILITY OF SUCH DAMAGE.
3005437641Stnn */
3105437641Stnn
3205437641Stnn #include <sys/cdefs.h>
33*2e6662efStnn __KERNEL_RCSID(0, "$NetBSD: ssdfb_i2c.c,v 1.11 2021/08/05 22:31:20 tnn Exp $");
3405437641Stnn
3505437641Stnn #include <sys/param.h>
3605437641Stnn #include <sys/device.h>
3705437641Stnn #include <dev/wscons/wsdisplayvar.h>
3805437641Stnn #include <dev/rasops/rasops.h>
3905437641Stnn #include <dev/i2c/i2cvar.h>
4005437641Stnn #include <dev/ic/ssdfbvar.h>
4105437641Stnn
4205437641Stnn struct ssdfb_i2c_softc {
4305437641Stnn struct ssdfb_softc sc;
4405437641Stnn i2c_tag_t sc_i2c_tag;
4505437641Stnn i2c_addr_t sc_i2c_addr;
46a12cabe2Stnn size_t sc_transfer_size;
4705437641Stnn };
4805437641Stnn
4905437641Stnn static int ssdfb_i2c_match(device_t, cfdata_t, void *);
5005437641Stnn static void ssdfb_i2c_attach(device_t, device_t, void *);
5105437641Stnn static int ssdfb_i2c_detach(device_t, int);
5205437641Stnn
53a12cabe2Stnn static int ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *, bool);
54a12cabe2Stnn static int ssdfb_i2c_transfer(struct ssdfb_i2c_softc *, uint8_t, uint8_t *,
55a12cabe2Stnn size_t, int);
5605437641Stnn static int ssdfb_i2c_cmd(void *, uint8_t *, size_t, bool);
5705437641Stnn static int ssdfb_i2c_transfer_rect(void *, uint8_t, uint8_t, uint8_t,
5805437641Stnn uint8_t, uint8_t *, size_t, bool);
5905437641Stnn static int ssdfb_i2c_transfer_rect_ssd1306(void *, uint8_t, uint8_t,
6005437641Stnn uint8_t, uint8_t, uint8_t *, size_t, bool);
6105437641Stnn static int ssdfb_i2c_transfer_rect_sh1106(void *, uint8_t, uint8_t,
6205437641Stnn uint8_t, uint8_t, uint8_t *, size_t, bool);
6305437641Stnn static int ssdfb_smbus_transfer_rect(void *, uint8_t, uint8_t, uint8_t,
6405437641Stnn uint8_t, uint8_t *, size_t, bool);
6505437641Stnn
6605437641Stnn CFATTACH_DECL_NEW(ssdfb_iic, sizeof(struct ssdfb_i2c_softc),
6705437641Stnn ssdfb_i2c_match, ssdfb_i2c_attach, ssdfb_i2c_detach, NULL);
6805437641Stnn
6905437641Stnn static const struct device_compatible_entry compat_data[] = {
70a9b41cc9Sthorpej { .compat = "solomon,ssd1306fb-i2c",
71a9b41cc9Sthorpej .value = SSDFB_PRODUCT_SSD1306_GENERIC },
72a9b41cc9Sthorpej
73a9b41cc9Sthorpej { .compat = "sino,sh1106fb-i2c",
74a9b41cc9Sthorpej .value = SSDFB_PRODUCT_SH1106_GENERIC },
75a9b41cc9Sthorpej
7618f3098cSthorpej DEVICE_COMPAT_EOL
7705437641Stnn };
7805437641Stnn
7905437641Stnn static int
ssdfb_i2c_match(device_t parent,cfdata_t match,void * aux)8005437641Stnn ssdfb_i2c_match(device_t parent, cfdata_t match, void *aux)
8105437641Stnn {
8205437641Stnn struct i2c_attach_args *ia = aux;
8305437641Stnn int match_result;
8405437641Stnn
8505437641Stnn if (iic_use_direct_match(ia, match, compat_data, &match_result))
8605437641Stnn return match_result;
8705437641Stnn
8805437641Stnn switch (ia->ia_addr) {
8905437641Stnn case SSDFB_I2C_DEFAULT_ADDR:
9005437641Stnn case SSDFB_I2C_ALTERNATIVE_ADDR:
9105437641Stnn return I2C_MATCH_ADDRESS_ONLY;
9205437641Stnn }
9305437641Stnn
9405437641Stnn return 0;
9505437641Stnn }
9605437641Stnn
9705437641Stnn static void
ssdfb_i2c_attach(device_t parent,device_t self,void * aux)9805437641Stnn ssdfb_i2c_attach(device_t parent, device_t self, void *aux)
9905437641Stnn {
10005437641Stnn struct ssdfb_i2c_softc *sc = device_private(self);
10105437641Stnn struct cfdata *cf = device_cfdata(self);
10205437641Stnn struct i2c_attach_args *ia = aux;
10305437641Stnn int flags = cf->cf_flags;
10405437641Stnn
10505437641Stnn if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN) {
106a9b41cc9Sthorpej const struct device_compatible_entry *dce =
107a9b41cc9Sthorpej iic_compatible_lookup(ia, compat_data);
108a9b41cc9Sthorpej if (dce != NULL) {
109a9b41cc9Sthorpej flags |= (int)dce->value;
11005437641Stnn }
11105437641Stnn }
11205437641Stnn if ((flags & SSDFB_ATTACH_FLAG_PRODUCT_MASK) == SSDFB_PRODUCT_UNKNOWN)
11305437641Stnn flags |= SSDFB_PRODUCT_SSD1306_GENERIC;
11405437641Stnn
115*2e6662efStnn flags |= SSDFB_ATTACH_FLAG_MPSAFE;
11605437641Stnn sc->sc.sc_dev = self;
11705437641Stnn sc->sc_i2c_tag = ia->ia_tag;
11805437641Stnn sc->sc_i2c_addr = ia->ia_addr;
11905437641Stnn sc->sc.sc_cookie = (void *)sc;
12005437641Stnn sc->sc.sc_cmd = ssdfb_i2c_cmd;
12105437641Stnn sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect;
12205437641Stnn
12305437641Stnn ssdfb_attach(&sc->sc, flags);
12405437641Stnn }
12505437641Stnn
12605437641Stnn static int
ssdfb_i2c_detach(device_t self,int flags)12705437641Stnn ssdfb_i2c_detach(device_t self, int flags)
12805437641Stnn {
12905437641Stnn struct ssdfb_i2c_softc *sc = device_private(self);
13005437641Stnn
13105437641Stnn return ssdfb_detach(&sc->sc);
13205437641Stnn }
13305437641Stnn
13405437641Stnn static int
ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc * sc,bool usepoll)135a12cabe2Stnn ssdfb_i2c_probe_transfer_size(struct ssdfb_i2c_softc *sc, bool usepoll)
136a12cabe2Stnn {
137a12cabe2Stnn int flags = usepoll ? I2C_F_POLL : 0;
138a12cabe2Stnn uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK;
139a12cabe2Stnn int error;
140a12cabe2Stnn uint8_t buf[128];
141a12cabe2Stnn size_t len;
142a12cabe2Stnn
143a12cabe2Stnn error = iic_acquire_bus(sc->sc_i2c_tag, flags);
144a12cabe2Stnn if (error)
145a12cabe2Stnn return error;
146a12cabe2Stnn len = sizeof(buf);
147a12cabe2Stnn memset(buf, 0, len);
148a12cabe2Stnn while (len > 0) {
149a12cabe2Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
150a12cabe2Stnn sc->sc_i2c_addr, &cb, sizeof(cb), buf, len, flags);
151a12cabe2Stnn if (!error) {
152a12cabe2Stnn break;
153a12cabe2Stnn }
154a12cabe2Stnn len >>= 1;
155a12cabe2Stnn }
156a12cabe2Stnn if (!error && len < 2) {
157a12cabe2Stnn error = E2BIG;
158a12cabe2Stnn } else {
159a12cabe2Stnn sc->sc_transfer_size = len;
160a12cabe2Stnn }
161a12cabe2Stnn (void) iic_release_bus(sc->sc_i2c_tag, flags);
162a12cabe2Stnn
163a12cabe2Stnn return error;
164a12cabe2Stnn }
165a12cabe2Stnn
166a12cabe2Stnn static int
ssdfb_i2c_transfer(struct ssdfb_i2c_softc * sc,uint8_t cb,uint8_t * data,size_t len,int flags)167a12cabe2Stnn ssdfb_i2c_transfer(struct ssdfb_i2c_softc *sc, uint8_t cb, uint8_t *data,
168a12cabe2Stnn size_t len, int flags)
169a12cabe2Stnn {
170a12cabe2Stnn int error;
171a12cabe2Stnn size_t xfer_size = sc->sc_transfer_size;
172a12cabe2Stnn
173a12cabe2Stnn while (len >= xfer_size) {
174a12cabe2Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
175a12cabe2Stnn sc->sc_i2c_addr, &cb, sizeof(cb), data, xfer_size, flags);
176a12cabe2Stnn if (error)
177a12cabe2Stnn return error;
178a12cabe2Stnn len -= xfer_size;
179a12cabe2Stnn data += xfer_size;
180a12cabe2Stnn }
181a12cabe2Stnn if (len > 0) {
182a12cabe2Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
183a12cabe2Stnn sc->sc_i2c_addr, &cb, sizeof(cb), data, len, flags);
184a12cabe2Stnn }
185a12cabe2Stnn
186a12cabe2Stnn return error;
187a12cabe2Stnn }
188a12cabe2Stnn
189a12cabe2Stnn static int
ssdfb_i2c_cmd(void * cookie,uint8_t * cmd,size_t len,bool usepoll)19005437641Stnn ssdfb_i2c_cmd(void *cookie, uint8_t *cmd, size_t len, bool usepoll)
19105437641Stnn {
19205437641Stnn struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
19305437641Stnn int flags = usepoll ? I2C_F_POLL : 0;
19405437641Stnn uint8_t cb = 0;
19505437641Stnn int error;
19605437641Stnn
19705437641Stnn error = iic_acquire_bus(sc->sc_i2c_tag, flags);
19805437641Stnn if (error)
19905437641Stnn return error;
20005437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
20105437641Stnn sc->sc_i2c_addr, &cb, sizeof(cb), cmd, len, flags);
20205437641Stnn (void) iic_release_bus(sc->sc_i2c_tag, flags);
20305437641Stnn
20405437641Stnn return error;
20505437641Stnn }
20605437641Stnn
20705437641Stnn static int
ssdfb_i2c_transfer_rect(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t frompage,uint8_t topage,uint8_t * p,size_t stride,bool usepoll)20805437641Stnn ssdfb_i2c_transfer_rect(void *cookie, uint8_t fromcol, uint8_t tocol,
20905437641Stnn uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
21005437641Stnn {
21105437641Stnn struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
21205437641Stnn uint8_t cmd[2];
21305437641Stnn int error;
21405437641Stnn
21505437641Stnn /*
21605437641Stnn * Test if large transfers are supported by the parent i2c bus and
21705437641Stnn * pick the fastest transfer routine for subsequent invocations.
21805437641Stnn */
21905437641Stnn switch (sc->sc.sc_p->p_controller_id) {
22005437641Stnn case SSDFB_CONTROLLER_SSD1306:
22105437641Stnn sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect_ssd1306;
22205437641Stnn break;
22305437641Stnn case SSDFB_CONTROLLER_SH1106:
22405437641Stnn sc->sc.sc_transfer_rect = ssdfb_i2c_transfer_rect_sh1106;
22505437641Stnn break;
22605437641Stnn default:
22705437641Stnn sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect;
22805437641Stnn break;
22905437641Stnn }
23005437641Stnn
23105437641Stnn if (sc->sc.sc_transfer_rect != ssdfb_smbus_transfer_rect) {
232a12cabe2Stnn error = ssdfb_i2c_probe_transfer_size(sc, usepoll);
23305437641Stnn if (error)
23405437641Stnn return error;
235a12cabe2Stnn aprint_verbose_dev(sc->sc.sc_dev, "%zd-byte transfers\n",
236a12cabe2Stnn sc->sc_transfer_size);
237a12cabe2Stnn if (sc->sc_transfer_size == 2) {
23805437641Stnn sc->sc.sc_transfer_rect = ssdfb_smbus_transfer_rect;
23905437641Stnn }
24005437641Stnn }
24105437641Stnn
24205437641Stnn /*
24305437641Stnn * Set addressing mode for SSD1306.
24405437641Stnn */
24505437641Stnn if (sc->sc.sc_p->p_controller_id == SSDFB_CONTROLLER_SSD1306) {
24605437641Stnn cmd[0] = SSD1306_CMD_SET_MEMORY_ADDRESSING_MODE;
24705437641Stnn cmd[1] = sc->sc.sc_transfer_rect
24805437641Stnn == ssdfb_i2c_transfer_rect_ssd1306
24905437641Stnn ? SSD1306_MEMORY_ADDRESSING_MODE_HORIZONTAL
25005437641Stnn : SSD1306_MEMORY_ADDRESSING_MODE_PAGE;
25105437641Stnn error = ssdfb_i2c_cmd(cookie, cmd, sizeof(cmd), usepoll);
25205437641Stnn if (error)
25305437641Stnn return error;
25405437641Stnn }
25505437641Stnn
25605437641Stnn return sc->sc.sc_transfer_rect(cookie, fromcol, tocol, frompage, topage,
25705437641Stnn p, stride, usepoll);
25805437641Stnn }
25905437641Stnn
26005437641Stnn
26105437641Stnn static int
ssdfb_i2c_transfer_rect_ssd1306(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t frompage,uint8_t topage,uint8_t * p,size_t stride,bool usepoll)26205437641Stnn ssdfb_i2c_transfer_rect_ssd1306(void *cookie, uint8_t fromcol, uint8_t tocol,
26305437641Stnn uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
26405437641Stnn {
26505437641Stnn struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
26605437641Stnn int flags = usepoll ? I2C_F_POLL : 0;
26705437641Stnn uint8_t cc = 0;
26805437641Stnn uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK;
269d673dfe7Stnn size_t len = tocol + 1 - fromcol;
27005437641Stnn int error;
27105437641Stnn /*
27205437641Stnn * SSD1306 does not implement the Continuation bit correctly.
27305437641Stnn * The SH1106 protocol defines that a control byte WITH Co
27405437641Stnn * set must be inserted between each command. But SSD1306
27505437641Stnn * fails to parse the commands if we do that.
27605437641Stnn */
27705437641Stnn uint8_t cmds[] = {
27805437641Stnn SSD1306_CMD_SET_COLUMN_ADDRESS,
27905437641Stnn fromcol, tocol,
28005437641Stnn SSD1306_CMD_SET_PAGE_ADDRESS,
28105437641Stnn frompage, topage
28205437641Stnn };
28305437641Stnn
28405437641Stnn error = iic_acquire_bus(sc->sc_i2c_tag, flags);
28505437641Stnn if (error)
28605437641Stnn return error;
28705437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
28805437641Stnn sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags);
28905437641Stnn if (error)
29005437641Stnn goto out;
29105437641Stnn while (frompage <= topage) {
292a12cabe2Stnn error = ssdfb_i2c_transfer(sc, cb, p, len, flags);
29305437641Stnn if (error)
29405437641Stnn goto out;
29505437641Stnn frompage++;
29605437641Stnn p += stride;
29705437641Stnn }
29805437641Stnn out:
29905437641Stnn (void) iic_release_bus(sc->sc_i2c_tag, flags);
30005437641Stnn
30105437641Stnn return error;
30205437641Stnn }
30305437641Stnn
30405437641Stnn static int
ssdfb_i2c_transfer_rect_sh1106(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t frompage,uint8_t topage,uint8_t * p,size_t stride,bool usepoll)30505437641Stnn ssdfb_i2c_transfer_rect_sh1106(void *cookie, uint8_t fromcol, uint8_t tocol,
30605437641Stnn uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
30705437641Stnn {
30805437641Stnn struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
30905437641Stnn int flags = usepoll ? I2C_F_POLL : 0;
31005437641Stnn uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK;
31105437641Stnn uint8_t cc = SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK;
312d673dfe7Stnn size_t len = tocol + 1 - fromcol;
31305437641Stnn int error;
31405437641Stnn uint8_t cmds[] = {
31505437641Stnn SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage,
31605437641Stnn SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK,
31705437641Stnn SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4),
31805437641Stnn SSDFB_I2C_CTRL_BYTE_CONTINUATION_MASK,
31905437641Stnn SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf)
32005437641Stnn };
32105437641Stnn
32205437641Stnn error = iic_acquire_bus(sc->sc_i2c_tag, flags);
32305437641Stnn if (error)
32405437641Stnn return error;
32505437641Stnn while (frompage <= topage) {
32605437641Stnn cmds[0] = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage;
32705437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
32805437641Stnn sc->sc_i2c_addr, &cc, sizeof(cc), cmds, sizeof(cmds), flags);
32905437641Stnn if (error)
33005437641Stnn goto out;
331a12cabe2Stnn error = ssdfb_i2c_transfer(sc, cb, p, len, flags);
33205437641Stnn if (error)
33305437641Stnn goto out;
33405437641Stnn frompage++;
33505437641Stnn p += stride;
33605437641Stnn }
33705437641Stnn out:
33805437641Stnn (void) iic_release_bus(sc->sc_i2c_tag, flags);
33905437641Stnn
34005437641Stnn return error;
34105437641Stnn }
34205437641Stnn
34305437641Stnn /*
34405437641Stnn * If the parent is an SMBus, then we can only send 2 bytes
34505437641Stnn * of payload per txn. The SSD1306 triple byte commands are
34605437641Stnn * not available so we have to use PAGE addressing mode
34705437641Stnn * and split data into multiple txns.
34805437641Stnn * This is ugly and slow but it's the best we can do.
34905437641Stnn */
35005437641Stnn static int
ssdfb_smbus_transfer_rect(void * cookie,uint8_t fromcol,uint8_t tocol,uint8_t frompage,uint8_t topage,uint8_t * p,size_t stride,bool usepoll)35105437641Stnn ssdfb_smbus_transfer_rect(void *cookie, uint8_t fromcol, uint8_t tocol,
35205437641Stnn uint8_t frompage, uint8_t topage, uint8_t *p, size_t stride, bool usepoll)
35305437641Stnn {
35405437641Stnn struct ssdfb_i2c_softc *sc = (struct ssdfb_i2c_softc *)cookie;
35505437641Stnn int flags = usepoll ? I2C_F_POLL : 0;
35605437641Stnn uint8_t cb = SSDFB_I2C_CTRL_BYTE_DATA_MASK;
35705437641Stnn uint8_t cc = 0;
358d673dfe7Stnn size_t len = tocol + 1 - fromcol;
35905437641Stnn uint8_t cmd_higher_col =
36005437641Stnn SSDFB_CMD_SET_HIGHER_COLUMN_START_ADDRESS_BASE + (fromcol >> 4);
36105437641Stnn uint8_t cmd_lower_col =
36205437641Stnn SSDFB_CMD_SET_LOWER_COLUMN_START_ADDRESS_BASE + (fromcol & 0xf);
36305437641Stnn uint8_t cmd_page;
36405437641Stnn uint8_t data[2];
36505437641Stnn uint8_t *colp;
36605437641Stnn uint8_t *endp;
36705437641Stnn int error;
36805437641Stnn
36905437641Stnn error = iic_acquire_bus(sc->sc_i2c_tag, flags);
37005437641Stnn if (error)
37105437641Stnn return error;
37205437641Stnn while (frompage <= topage) {
37305437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
37405437641Stnn sc->sc_i2c_addr, &cc, sizeof(cc),
37505437641Stnn &cmd_higher_col, sizeof(cmd_higher_col), flags);
37605437641Stnn if (error)
37705437641Stnn goto out;
37805437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
37905437641Stnn sc->sc_i2c_addr, &cc, sizeof(cc),
38005437641Stnn &cmd_lower_col, sizeof(cmd_lower_col), flags);
38105437641Stnn if (error)
38205437641Stnn goto out;
38305437641Stnn cmd_page = SSDFB_CMD_SET_PAGE_START_ADDRESS_BASE + frompage;
38405437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
38505437641Stnn sc->sc_i2c_addr, &cc, sizeof(cc),
38605437641Stnn &cmd_page, sizeof(cmd_page), flags);
38705437641Stnn if (error)
38805437641Stnn goto out;
38905437641Stnn colp = p;
39005437641Stnn endp = colp + len;
39105437641Stnn if (len & 1) {
39205437641Stnn data[0] = *colp++;
39305437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
39405437641Stnn sc->sc_i2c_addr, &cb, sizeof(cb), data, 1, flags);
39505437641Stnn if (error)
39605437641Stnn goto out;
39705437641Stnn }
39805437641Stnn while (colp < endp) {
39905437641Stnn /*
40005437641Stnn * Send two bytes at a time. We can't use colp directly
40105437641Stnn * because i2c controllers sometimes have data alignment
40205437641Stnn * requirements.
40305437641Stnn */
40405437641Stnn data[0] = *colp++;
40505437641Stnn data[1] = *colp++;
40605437641Stnn error = iic_exec(sc->sc_i2c_tag, I2C_OP_WRITE_WITH_STOP,
40705437641Stnn sc->sc_i2c_addr, &cb, sizeof(cb), data, 2, flags);
40805437641Stnn if (error)
40905437641Stnn goto out;
41005437641Stnn }
41105437641Stnn frompage++;
41205437641Stnn p += stride;
41305437641Stnn }
41405437641Stnn out:
41505437641Stnn (void) iic_release_bus(sc->sc_i2c_tag, flags);
416a12cabe2Stnn
41705437641Stnn return error;
41805437641Stnn }
419