paul@114 | 1 | /* |
paul@114 | 2 | * Access peripherals on the I2C bus. |
paul@114 | 3 | * |
paul@114 | 4 | * Copyright (C) 2018, 2020 Paul Boddie <paul@boddie.org.uk> |
paul@114 | 5 | * |
paul@114 | 6 | * This program is free software; you can redistribute it and/or |
paul@114 | 7 | * modify it under the terms of the GNU General Public License as |
paul@114 | 8 | * published by the Free Software Foundation; either version 2 of |
paul@114 | 9 | * the License, or (at your option) any later version. |
paul@114 | 10 | * |
paul@114 | 11 | * This program is distributed in the hope that it will be useful, |
paul@114 | 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
paul@114 | 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
paul@114 | 14 | * GNU General Public License for more details. |
paul@114 | 15 | * |
paul@114 | 16 | * You should have received a copy of the GNU General Public License |
paul@114 | 17 | * along with this program; if not, write to the Free Software |
paul@114 | 18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, |
paul@114 | 19 | * Boston, MA 02110-1301, USA |
paul@114 | 20 | */ |
paul@114 | 21 | |
paul@114 | 22 | #include <l4/devices/cpm-jz4730.h> |
paul@114 | 23 | #include <l4/devices/i2c-jz4730.h> |
paul@114 | 24 | #include <l4/devices/memory.h> |
paul@114 | 25 | |
paul@118 | 26 | #include <l4/re/c/util/cap_alloc.h> |
paul@118 | 27 | #include <l4/sys/factory.h> |
paul@118 | 28 | #include <l4/sys/icu.h> |
paul@118 | 29 | #include <l4/sys/irq.h> |
paul@118 | 30 | #include <l4/sys/rcv_endpoint.h> |
paul@118 | 31 | |
paul@114 | 32 | #include <stdio.h> |
paul@118 | 33 | #include <unistd.h> |
paul@114 | 34 | |
paul@114 | 35 | |
paul@114 | 36 | |
paul@118 | 37 | /* Device and resource discovery. */ |
paul@118 | 38 | |
paul@118 | 39 | static long item_in_range(long start, long end, long index) |
paul@118 | 40 | { |
paul@118 | 41 | if (start < end) |
paul@118 | 42 | return start + index; |
paul@118 | 43 | else |
paul@118 | 44 | return start - index; |
paul@118 | 45 | } |
paul@118 | 46 | |
paul@114 | 47 | /* Scan the I2C bus by performing speculative reads from each device address. */ |
paul@114 | 48 | |
paul@114 | 49 | static void i2c_scan(void *i2c_channel) |
paul@114 | 50 | { |
paul@114 | 51 | uint8_t buf[1]; |
paul@114 | 52 | unsigned int address; |
paul@114 | 53 | |
paul@114 | 54 | for (address = 0; address < 0x20; address++) |
paul@114 | 55 | printf("%02x ", address); |
paul@114 | 56 | printf("\n"); |
paul@114 | 57 | |
paul@114 | 58 | for (address = 0; address < 0x20; address++) |
paul@114 | 59 | printf("-- "); |
paul@114 | 60 | |
paul@114 | 61 | for (address = 0; address < 0x80; address++) |
paul@114 | 62 | { |
paul@114 | 63 | if ((address % 32) == 0) |
paul@114 | 64 | printf("\n"); |
paul@114 | 65 | |
paul@114 | 66 | if (jz4730_i2c_read(i2c_channel, address, buf, 1)) |
paul@114 | 67 | printf("%02x ", address); |
paul@114 | 68 | else |
paul@114 | 69 | printf("?? "); |
paul@114 | 70 | } |
paul@114 | 71 | |
paul@114 | 72 | printf("\n"); |
paul@114 | 73 | for (address = 0; address < 0x20; address++) |
paul@114 | 74 | printf("-- "); |
paul@114 | 75 | printf("\n\n"); |
paul@114 | 76 | } |
paul@114 | 77 | |
paul@114 | 78 | /* Interpret RTC registers. */ |
paul@114 | 79 | |
paul@114 | 80 | static void rtc_datetime(uint8_t *rtcregs) |
paul@114 | 81 | { |
paul@114 | 82 | const char *weekdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; |
paul@114 | 83 | |
paul@114 | 84 | printf("%s %d%d-%d%d-%d%d %d%d:%d%d:%d%d\n", |
paul@114 | 85 | weekdays[rtcregs[0x06] & 0x07], |
paul@114 | 86 | (rtcregs[0x08] & 0xf0) >> 4, |
paul@114 | 87 | rtcregs[0x08] & 0x0f, |
paul@114 | 88 | (rtcregs[0x07] & 0x10) >> 4, |
paul@114 | 89 | rtcregs[0x07] & 0x0f, |
paul@114 | 90 | (rtcregs[0x05] & 0x30) >> 4, |
paul@114 | 91 | rtcregs[0x05] & 0x0f, |
paul@114 | 92 | (rtcregs[0x04] & 0x30) >> 4, |
paul@114 | 93 | rtcregs[0x04] & 0x0f, |
paul@114 | 94 | (rtcregs[0x03] & 0x70) >> 4, |
paul@114 | 95 | rtcregs[0x03] & 0x0f, |
paul@114 | 96 | (rtcregs[0x02] & 0x70) >> 4, |
paul@114 | 97 | rtcregs[0x02] & 0x0f); |
paul@114 | 98 | } |
paul@114 | 99 | |
paul@114 | 100 | |
paul@114 | 101 | |
paul@114 | 102 | int main(void) |
paul@114 | 103 | { |
paul@114 | 104 | void *cpm; |
paul@114 | 105 | void *i2c, *i2c0; |
paul@114 | 106 | |
paul@118 | 107 | /* Interrupts. */ |
paul@118 | 108 | |
paul@118 | 109 | l4_uint32_t i2c_irq_start = 0, i2c_irq_end = 0; |
paul@118 | 110 | l4_cap_idx_t icucap, irq0cap; |
paul@118 | 111 | |
paul@118 | 112 | /* Obtain resource details describing the interrupt for I2C channel 0. */ |
paul@118 | 113 | |
paul@118 | 114 | printf("Access IRQ...\n"); |
paul@118 | 115 | |
paul@118 | 116 | if (get_irq("jz4730-i2c", &i2c_irq_start, &i2c_irq_end) < 0) |
paul@118 | 117 | return 1; |
paul@118 | 118 | |
paul@118 | 119 | printf("IRQ range at %d...%d.\n", i2c_irq_start, i2c_irq_end); |
paul@118 | 120 | |
paul@118 | 121 | /* Obtain capabilities for the interrupt controller and an interrupt. */ |
paul@118 | 122 | |
paul@118 | 123 | irq0cap = l4re_util_cap_alloc(); |
paul@118 | 124 | icucap = l4re_env_get_cap("icu"); |
paul@118 | 125 | |
paul@118 | 126 | if (l4_is_invalid_cap(icucap)) |
paul@118 | 127 | { |
paul@118 | 128 | printf("No 'icu' capability available in the virtual bus.\n"); |
paul@118 | 129 | return 1; |
paul@118 | 130 | } |
paul@118 | 131 | |
paul@118 | 132 | if (l4_is_invalid_cap(irq0cap)) |
paul@118 | 133 | { |
paul@118 | 134 | printf("Capabilities not available for interrupts.\n"); |
paul@118 | 135 | return 1; |
paul@118 | 136 | } |
paul@118 | 137 | |
paul@118 | 138 | /* Create interrupt objects. */ |
paul@118 | 139 | |
paul@118 | 140 | long err; |
paul@118 | 141 | |
paul@118 | 142 | err = l4_error(l4_factory_create_irq(l4re_global_env->factory, irq0cap)); |
paul@118 | 143 | |
paul@118 | 144 | if (err) |
paul@118 | 145 | { |
paul@118 | 146 | printf("Could not create IRQ object: %lx\n", err); |
paul@118 | 147 | return 1; |
paul@118 | 148 | } |
paul@118 | 149 | |
paul@118 | 150 | /* Bind interrupt objects to IRQ numbers. */ |
paul@118 | 151 | |
paul@118 | 152 | err = l4_error(l4_icu_bind(icucap, |
paul@118 | 153 | item_in_range(i2c_irq_start, i2c_irq_end, 0), |
paul@118 | 154 | irq0cap)); |
paul@118 | 155 | |
paul@118 | 156 | if (err) |
paul@118 | 157 | { |
paul@118 | 158 | printf("Could not bind IRQ to the ICU: %ld\n", err); |
paul@118 | 159 | return 1; |
paul@118 | 160 | } |
paul@118 | 161 | |
paul@118 | 162 | /* Attach ourselves to the interrupt handler. */ |
paul@118 | 163 | |
paul@118 | 164 | err = l4_error(l4_rcv_ep_bind_thread(irq0cap, l4re_env()->main_thread, 0)); |
paul@118 | 165 | |
paul@118 | 166 | if (err) |
paul@118 | 167 | { |
paul@118 | 168 | printf("Could not attach to IRQs: %ld\n", err); |
paul@118 | 169 | return 1; |
paul@118 | 170 | } |
paul@118 | 171 | |
paul@114 | 172 | /* Peripheral memory. */ |
paul@114 | 173 | |
paul@114 | 174 | l4_addr_t cpm_base = 0, cpm_base_end = 0; |
paul@114 | 175 | l4_addr_t i2c_base = 0, i2c_base_end = 0; |
paul@114 | 176 | |
paul@114 | 177 | /* Obtain resource details describing I/O memory. */ |
paul@114 | 178 | |
paul@114 | 179 | printf("Access CPM...\n"); |
paul@114 | 180 | |
paul@114 | 181 | if (get_memory("jz4730-cpm", &cpm_base, &cpm_base_end) < 0) |
paul@114 | 182 | return 1; |
paul@114 | 183 | |
paul@114 | 184 | printf("CPM at 0x%lx...0x%lx.\n", cpm_base, cpm_base_end); |
paul@114 | 185 | |
paul@114 | 186 | printf("Access I2C...\n"); |
paul@114 | 187 | |
paul@114 | 188 | if (get_memory("jz4730-i2c", &i2c_base, &i2c_base_end) < 0) |
paul@114 | 189 | return 1; |
paul@114 | 190 | |
paul@114 | 191 | printf("I2C at 0x%lx...0x%lx.\n", i2c_base, i2c_base_end); |
paul@114 | 192 | |
paul@114 | 193 | /* Obtain CPM and I2C references. */ |
paul@114 | 194 | |
paul@114 | 195 | cpm = jz4730_cpm_init(cpm_base); |
paul@114 | 196 | i2c = jz4730_i2c_init(i2c_base, i2c_base_end, cpm, 100000); /* 100 kHz */ |
paul@118 | 197 | i2c0 = jz4730_i2c_get_channel(i2c, 0, irq0cap); |
paul@114 | 198 | |
paul@114 | 199 | /* Enable I2C. */ |
paul@114 | 200 | |
paul@114 | 201 | jz4730_i2c_enable(i2c0); |
paul@114 | 202 | |
paul@114 | 203 | uint8_t buf[32]; |
paul@114 | 204 | unsigned int nwritten; |
paul@114 | 205 | |
paul@114 | 206 | /* Set the clock: 12:34:56 on Saturday 2nd January 2021. */ |
paul@114 | 207 | |
paul@114 | 208 | buf[0] = 2; buf[1] = 0x56; buf[2] = 0x34; buf[3] = 0x12; buf[4] = 0x02; buf[5] = 0x06; buf[6] = 0x01; buf[7] = 0x21; |
paul@114 | 209 | nwritten = jz4730_i2c_write(i2c0, 0x51, buf, 8); |
paul@114 | 210 | |
paul@114 | 211 | printf("Written: %d\n", nwritten); |
paul@114 | 212 | |
paul@120 | 213 | for (int i = 0; i < 3; i++) |
paul@118 | 214 | { |
paul@118 | 215 | /* Issue selection of device register 0. */ |
paul@114 | 216 | |
paul@118 | 217 | buf[0] = 0; |
paul@118 | 218 | nwritten = jz4730_i2c_write(i2c0, 0x51, buf, 1); |
paul@118 | 219 | |
paul@118 | 220 | printf("Written: %d\n", nwritten); |
paul@114 | 221 | |
paul@118 | 222 | /* Read the contents of 16 registers. */ |
paul@118 | 223 | |
paul@118 | 224 | unsigned int nread = jz4730_i2c_read(i2c0, 0x51, buf, 16); |
paul@114 | 225 | |
paul@118 | 226 | printf("Read: %d\n", nread); |
paul@118 | 227 | for (unsigned int i = 0; i < nread; i++) |
paul@118 | 228 | printf("%02x ", buf[i]); |
paul@118 | 229 | printf("\n"); |
paul@114 | 230 | |
paul@118 | 231 | /* Show the interpreted date and time. */ |
paul@118 | 232 | |
paul@118 | 233 | rtc_datetime(buf); |
paul@114 | 234 | |
paul@120 | 235 | /* Read from the power controller at 0x28. */ |
paul@120 | 236 | |
paul@120 | 237 | buf[0] = 0xdb; |
paul@120 | 238 | nwritten = jz4730_i2c_write(i2c0, 0x28, buf, 1); |
paul@120 | 239 | printf("Written: %d\n", nwritten); |
paul@120 | 240 | nread = jz4730_i2c_read(i2c0, 0x28, buf, 1); |
paul@120 | 241 | printf("Read: %d\n", nread); |
paul@120 | 242 | |
paul@120 | 243 | printf("Voltage level: %1.3f\n", buf[0] * 36235 / 1000000.0); |
paul@120 | 244 | |
paul@120 | 245 | buf[0] = 0xd9; |
paul@120 | 246 | nwritten = jz4730_i2c_write(i2c0, 0x28, buf, 1); |
paul@120 | 247 | printf("Written: %d\n", nwritten); |
paul@120 | 248 | nread = jz4730_i2c_read(i2c0, 0x28, buf, 1); |
paul@120 | 249 | printf("Read: %d\n", nread); |
paul@120 | 250 | |
paul@120 | 251 | printf("Charger connected: %s\n", buf[0] & 1 ? "yes" : "no"); |
paul@120 | 252 | |
paul@118 | 253 | /* Scan the bus. */ |
paul@118 | 254 | |
paul@118 | 255 | printf("Scan I2C0...\n"); |
paul@118 | 256 | i2c_scan(i2c0); |
paul@114 | 257 | |
paul@118 | 258 | sleep(5); |
paul@118 | 259 | } |
paul@120 | 260 | |
paul@137 | 261 | printf("start_not_possible = %d\n" |
paul@137 | 262 | "read_not_ready = %d\n" |
paul@137 | 263 | "read_not_ready_clear = %d\n" |
paul@137 | 264 | "read_not_ready_stx = %d\n" |
paul@137 | 265 | "read_not_possible = %d\n" |
paul@137 | 266 | "read_nack = %d\n" |
paul@137 | 267 | "read_complete = %d\n" |
paul@137 | 268 | "write_not_possible = %d\n" |
paul@137 | 269 | "write_complete = %d\n" |
paul@137 | 270 | "total_reads = %d\n", |
paul@137 | 271 | jz4730_i2c_start_not_possible(i2c0), jz4730_i2c_read_not_ready(i2c0), jz4730_i2c_read_not_ready_clear(i2c0), jz4730_i2c_read_not_ready_stx(i2c0), |
paul@137 | 272 | jz4730_i2c_read_not_possible(i2c0), jz4730_i2c_read_nack(i2c0), jz4730_i2c_read_complete(i2c0), |
paul@137 | 273 | jz4730_i2c_write_not_possible(i2c0), jz4730_i2c_write_complete(i2c0), jz4730_i2c_total_reads(i2c0)); |
paul@137 | 274 | |
paul@137 | 275 | sleep(30); |
paul@137 | 276 | |
paul@120 | 277 | /* Issue a shutdown request. */ |
paul@120 | 278 | |
paul@120 | 279 | buf[0] = 0xd8; buf[1] = 1; |
paul@120 | 280 | nwritten = jz4730_i2c_write(i2c0, 0x28, buf, 2); |
paul@120 | 281 | printf("Written: %d\n", nwritten); |
paul@114 | 282 | |
paul@118 | 283 | /* Detach from the interrupt. */ |
paul@114 | 284 | |
paul@118 | 285 | err = l4_error(l4_irq_detach(irq0cap)); |
paul@118 | 286 | |
paul@118 | 287 | if (err) |
paul@118 | 288 | printf("Error detaching from IRQ objects: %ld\n", err); |
paul@114 | 289 | |
paul@114 | 290 | return 0; |
paul@114 | 291 | } |