| 1 | /* |
| 2 | * QEMU model of the NXP FLEXCAN device. |
| 3 | * |
| 4 | * This implementation is based on the following reference manual: |
| 5 | * i.MX 6Dual/6Quad Applications Processor Reference Manual |
| 6 | * Document Number: IMX6DQRM, Rev. 6, 05/2020 |
| 7 | * |
| 8 | * Copyright (c) 2025 Matyas Bobek <matyas.bobek@gmail.com> |
| 9 | * |
| 10 | * Based on CTU CAN FD emulation implemented by Jan Charvat. |
| 11 | * |
| 12 | * SPDX-License-Identifier: GPL-2.0-or-later |
| 13 | */ |
| 14 | |
| 15 | #include "qemu/osdep.h" |
| 16 | #include "qemu/log.h" |
| 17 | #include "hw/core/sysbus.h" |
| 18 | #include "qapi/error.h" |
| 19 | #include "hw/core/irq.h" |
| 20 | #include "migration/vmstate.h" |
| 21 | #include "net/can_emu.h" |
| 22 | #include "hw/core/qdev-properties.h" |
| 23 | #include "trace.h" |
| 24 | |
| 25 | #include "hw/net/flexcan.h" |
| 26 | #include "flexcan_regs.h" |
| 27 | #include "qemu/timer.h" |
| 28 | |
| 29 | /* |
| 30 | * Indicates MB w/ received frame has not been serviced yet |
| 31 | * This is an emulator-only flag in position of unused (reserved) bit |
| 32 | * of message buffer control register |
| 33 | */ |
| 34 | #define FLEXCAN_MB_CNT_NOT_SRV BIT(23) |
| 35 | /** |
| 36 | * if no MB is locked, FlexcanState.locked_mb |
| 37 | * is set to FLEXCAN_NO_MB_LOCKED |
| 38 | */ |
| 39 | #define FLEXCAN_NO_MB_LOCKED -1 |
| 40 | /** |
| 41 | * if no frame is waiting in the SMB, FlexcanState.smb_target_mbid |
| 42 | * is set to FLEXCAN_SMB_EMPTY |
| 43 | */ |
| 44 | #define FLEXCAN_SMB_EMPTY -1 |
| 45 | /** |
| 46 | * When the module is disabled or in freeze mode, |
| 47 | * the timer is not running. That is indicated by setting |
| 48 | * FlexcanState.timer_start to FLEXCAN_TIMER_STOPPED. |
| 49 | */ |
| 50 | #define FLEXCAN_TIMER_STOPPED -1 |
| 51 | |
| 52 | /* These constants are returned by flexcan_fifo_rx() and flexcan_mb_rx(), */ |
| 53 | enum FlexcanRx { |
| 54 | /* Retry the other receiving mechanism (ie. message bufer or mailbox). */ |
| 55 | FLEXCAN_RX_SEARCH_RETRY, |
| 56 | /* The frame was received and stored. */ |
| 57 | FLEXCAN_RX_SEARCH_ACCEPT, |
| 58 | /* The frame was filtered out and dropped. */ |
| 59 | FLEXCAN_RX_SEARCH_DROPPED, |
| 60 | }; |
| 61 | |
| 62 | /* |
| 63 | * These constants are returned by flexcan_mb_rx_check_mb(). |
| 64 | * See flexcan_mb_rx_check_mb() kerneldoc for details. |
| 65 | */ |
| 66 | enum FlexcanCheck { |
| 67 | FLEXCAN_CHECK_MB_NIL = 0, |
| 68 | FLEXCAN_CHECK_MB_MATCH = 3, |
| 69 | FLEXCAN_CHECK_MB_MATCH_NON_FREE = 1, |
| 70 | FLEXCAN_CHECK_MB_MATCH_LOCKED = 5, |
| 71 | }; |
| 72 | |
| 73 | static const FlexcanRegs flexcan_regs_write_mask = { |
| 74 | .mcr = 0xF6EB337F, |
| 75 | .ctrl = 0xFFFFFFFF, |
| 76 | .timer = 0xFFFFFFFF, |
| 77 | .tcr = 0xFFFFFFFF, |
| 78 | .rxmgmask = 0xFFFFFFFF, |
| 79 | .rx14mask = 0xFFFFFFFF, |
| 80 | .rx15mask = 0xFFFFFFFF, |
| 81 | .ecr = 0xFFFFFFFF, |
| 82 | .esr = 0xFFFFFFFF, |
| 83 | .imask2 = 0xFFFFFFFF, |
| 84 | .imask1 = 0xFFFFFFFF, |
| 85 | .iflag2 = 0, |
| 86 | .iflag1 = 0, |
| 87 | .ctrl2 = 0xFFFFFFFF, |
| 88 | .esr2 = 0, |
| 89 | .imeur = 0, |
| 90 | .lrfr = 0, |
| 91 | .crcr = 0, |
| 92 | .rxfgmask = 0xFFFFFFFF, |
| 93 | .rxfir = 0, |
| 94 | .cbt = 0, |
| 95 | ._reserved2 = 0, |
| 96 | .dbg1 = 0, |
| 97 | .dbg2 = 0, |
| 98 | .mbs = { [0 ... 63] = { |
| 99 | .can_ctrl = 0xFFFFFFFF & ~FLEXCAN_MB_CNT_NOT_SRV, |
| 100 | .can_id = 0xFFFFFFFF, |
| 101 | .data = { 0xFFFFFFFF, 0xFFFFFFFF }, |
| 102 | } }, |
| 103 | ._reserved4 = {0}, |
| 104 | .rximr = { [0 ... 63] = 0xFFFFFFFF }, |
| 105 | ._reserved5 = {0}, |
| 106 | .gfwr_mx6 = 0xFFFFFFFF, |
| 107 | ._reserved6 = {0}, |
| 108 | ._reserved8 = {0}, |
| 109 | .rx_smb0 = { |
| 110 | .can_ctrl = 0, |
| 111 | .can_id = 0, |
| 112 | .data = { 0, 0 }, |
| 113 | }, |
| 114 | .rx_smb1 = {0, 0, 0, 0}, |
| 115 | }; |
| 116 | static const FlexcanRegs flexcan_regs_reset_mask = { |
| 117 | .mcr = 0x80000000, |
| 118 | .ctrl = 0xFFFFFFFF, |
| 119 | .timer = 0, |
| 120 | .tcr = 0, |
| 121 | .rxmgmask = 0xFFFFFFFF, |
| 122 | .rx14mask = 0xFFFFFFFF, |
| 123 | .rx15mask = 0xFFFFFFFF, |
| 124 | .ecr = 0, |
| 125 | .esr = 0, |
| 126 | .imask2 = 0, |
| 127 | .imask1 = 0, |
| 128 | .iflag2 = 0, |
| 129 | .iflag1 = 0, |
| 130 | .ctrl2 = 0xFFFFFFFF, |
| 131 | .esr2 = 0, |
| 132 | .imeur = 0, |
| 133 | .lrfr = 0, |
| 134 | .crcr = 0, |
| 135 | .rxfgmask = 0xFFFFFFFF, |
| 136 | .rxfir = 0xFFFFFFFF, |
| 137 | .cbt = 0, |
| 138 | ._reserved2 = 0, |
| 139 | .dbg1 = 0, |
| 140 | .dbg2 = 0, |
| 141 | .mbs = { [0 ... FLEXCAN_MAILBOX_COUNT - 1] = { |
| 142 | .can_ctrl = 0xFFFFFFFF, |
| 143 | .can_id = 0xFFFFFFFF, |
| 144 | .data = { 0xFFFFFFFF, 0xFFFFFFFF }, |
| 145 | } }, |
| 146 | ._reserved4 = {0}, |
| 147 | .rximr = { [0 ... 63] = 0xFFFFFFFF }, |
| 148 | ._reserved5 = {0}, |
| 149 | .gfwr_mx6 = 0, |
| 150 | ._reserved6 = {0}, |
| 151 | ._reserved8 = {0}, |
| 152 | .rx_smb0 = { |
| 153 | .can_ctrl = 0, |
| 154 | .can_id = 0, |
| 155 | .data = { 0, 0 }, |
| 156 | }, |
| 157 | .rx_smb1 = {0, 0, 0, 0}, |
| 158 | }; |
| 159 | |
| 160 | /* length of buffer used to format register names in trace output */ |
| 161 | #define FLEXCAN_DBG_BUF_LEN 16 |
| 162 | |
| 163 | /** |
| 164 | * flexcan_dbg_mb_code_strs - Readable names for CODE field codes |
| 165 | * |
| 166 | * Readable names for possible values of CODE field in message buffer |
| 167 | * control word. |
| 168 | */ |
| 169 | static const char *flexcan_dbg_mb_code_strs[16] = { |
| 170 | "INACTIVE_RX", |
| 171 | "FULL", |
| 172 | "EMPTY", |
| 173 | "OVERRUN", |
| 174 | "INACTIVE_TX", |
| 175 | "RANSWER", |
| 176 | "DATA", |
| 177 | "TANSWER" |
| 178 | }; |
| 179 | |
| 180 | /** |
| 181 | * flexcan_dbg_mb_code() - Get the string representation of a mailbox code |
| 182 | * @mb_ctrl: The mailbox control register value |
| 183 | * @buf: The buffer to store the string representation |
| 184 | * |
| 185 | * Return: Either constant string or string formatted into @buf |
| 186 | */ |
| 187 | static const char *flexcan_dbg_mb_code(uint32_t mb_ctrl, char *buf) |
| 188 | { |
| 189 | uint32_t code = mb_ctrl & FLEXCAN_MB_CODE_MASK; |
| 190 | uint32_t code_idx = code >> 24; |
| 191 | if (code == FLEXCAN_MB_CODE_TX_ABORT) { |
| 192 | return "ABORT"; |
| 193 | } else { |
| 194 | const char *code_str = flexcan_dbg_mb_code_strs[code_idx >> 1]; |
| 195 | if (code_idx & 1) { |
| 196 | g_snprintf(buf, FLEXCAN_DBG_BUF_LEN, "%s+BUSY", code_str); |
| 197 | return buf; |
| 198 | } |
| 199 | |
| 200 | return code_str; |
| 201 | } |
| 202 | } |
| 203 | |
| 204 | static const char *flexcan_dbg_reg_name_fixed(hwaddr addr) |
| 205 | { |
| 206 | switch (addr) { |
| 207 | case offsetof(FlexcanRegs, mcr): |
| 208 | return "MCR"; |
| 209 | case offsetof(FlexcanRegs, ctrl): |
| 210 | return "CTRL"; |
| 211 | case offsetof(FlexcanRegs, timer): |
| 212 | return "TIMER"; |
| 213 | case offsetof(FlexcanRegs, esr): |
| 214 | return "ESR"; |
| 215 | case offsetof(FlexcanRegs, rxmgmask): |
| 216 | return "RXMGMASK"; |
| 217 | case offsetof(FlexcanRegs, rx14mask): |
| 218 | return "RX14MASK"; |
| 219 | case offsetof(FlexcanRegs, rx15mask): |
| 220 | return "RX15MASK"; |
| 221 | case offsetof(FlexcanRegs, rxfgmask): |
| 222 | return "RXFGMASK"; |
| 223 | case offsetof(FlexcanRegs, ecr): |
| 224 | return "ECR"; |
| 225 | case offsetof(FlexcanRegs, ctrl2): |
| 226 | return "CTRL2"; |
| 227 | case offsetof(FlexcanRegs, imask2): |
| 228 | return "IMASK2"; |
| 229 | case offsetof(FlexcanRegs, imask1): |
| 230 | return "IMASK1"; |
| 231 | case offsetof(FlexcanRegs, iflag2): |
| 232 | return "IFLAG2"; |
| 233 | case offsetof(FlexcanRegs, iflag1): |
| 234 | return "IFLAG1"; |
| 235 | } |
| 236 | return NULL; |
| 237 | } |
| 238 | |
| 239 | static inline void flexcan_trace_mem_op(FlexcanState *s, hwaddr addr, |
| 240 | uint32_t value, int size, bool is_wr) |
| 241 | { |
| 242 | if (trace_event_get_state_backends(TRACE_FLEXCAN_MEM_OP)) { |
| 243 | const char *reg_name = "unknown"; |
| 244 | char reg_name_buf[FLEXCAN_DBG_BUF_LEN] = { 0 }; |
| 245 | const char *reg_name_fixed = flexcan_dbg_reg_name_fixed(addr); |
| 246 | const char *op_string = is_wr ? "write" : "read"; |
| 247 | |
| 248 | if (reg_name_fixed) { |
| 249 | reg_name = reg_name_fixed; |
| 250 | } else if (addr >= 0x80 && addr < 0x480) { |
| 251 | int mbidx = (addr - 0x80) / 16; |
| 252 | g_snprintf(reg_name_buf, sizeof(reg_name_buf), "MB%i", mbidx); |
| 253 | reg_name = reg_name_buf; |
| 254 | } else if (addr >= 0x880 && addr < 0x9e0) { |
| 255 | int id = (addr - 0x880) / 4; |
| 256 | g_snprintf(reg_name_buf, sizeof(reg_name_buf), "RXIMR%i", id); |
| 257 | reg_name = reg_name_buf; |
| 258 | } |
| 259 | |
| 260 | trace_flexcan_mem_op(DEVICE(s)->canonical_path, op_string, value, addr, |
| 261 | reg_name, size); |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | static enum FlexcanRx flexcan_mb_rx(FlexcanState *s, |
| 266 | const qemu_can_frame *frame); |
| 267 | static void flexcan_mb_unlock(FlexcanState *s); |
| 268 | |
| 269 | /* ========== Mailbox Utils ========== */ |
| 270 | |
| 271 | /** |
| 272 | * flexcan_mailbox_count() - Get number of enabled mailboxes |
| 273 | * @s: FlexCAN device pointer |
| 274 | * |
| 275 | * Count is based on MCR[MAXMB] field. Note that some of those mailboxes |
| 276 | * might be part of queue or queue ID filters or ordinary message buffers. |
| 277 | */ |
| 278 | static inline int flexcan_enabled_mailbox_count(const FlexcanState *s) |
| 279 | { |
| 280 | return MIN((s->regs.mcr & FLEXCAN_MCR_MAXMB(UINT32_MAX)) + 1, |
| 281 | FLEXCAN_MAILBOX_COUNT); |
| 282 | } |
| 283 | |
| 284 | /** |
| 285 | * flexcan_get_first_message_buffer() - Get pointer to first message buffer |
| 286 | * @s: FlexCAN device pointer |
| 287 | * |
| 288 | * In context of this function, message buffer means a mailbox which is not |
| 289 | * a queue element nor a queue filter. Note this function does not take |
| 290 | * MCR[MAXMB] into account, meaning that the returned mailbox |
| 291 | * might be disabled. |
| 292 | */ |
| 293 | static FlexcanRegsMessageBuffer *flexcan_get_first_message_buffer( |
| 294 | FlexcanState *s) |
| 295 | { |
| 296 | if (s->regs.mcr & FLEXCAN_MCR_FEN) { |
| 297 | int rffn = (s->regs.ctrl2 & FLEXCAN_CTRL2_RFFN(UINT32_MAX)) >> 24; |
| 298 | return s->regs.mbs + 8 + 2 * rffn; |
| 299 | } |
| 300 | |
| 301 | return s->regs.mbs; |
| 302 | } |
| 303 | |
| 304 | /** |
| 305 | * flexcan_get_last_enabled_mailbox() - Get pointer to last enabled mailbox. |
| 306 | * @s: FlexCAN device pointer |
| 307 | * |
| 308 | * When used with flexcan_get_first_message_buffer(), all mailboxes *ptr in |
| 309 | * range `first_message_buffer() <= ptr <= last_enabled_mailbox` are valid |
| 310 | * message buffer mailboxes. |
| 311 | * |
| 312 | * Return: Last enabled mailbox in MCR[MAXMB] sense. The mailbox might be |
| 313 | * of any type. |
| 314 | */ |
| 315 | static inline FlexcanRegsMessageBuffer *flexcan_get_last_enabled_mailbox( |
| 316 | FlexcanState *s) |
| 317 | { |
| 318 | return s->regs.mbs + flexcan_enabled_mailbox_count(s); |
| 319 | } |
| 320 | |
| 321 | /* ========== Free-running Timer ========== */ |
| 322 | static inline int64_t flexcan_get_time(void) |
| 323 | { |
| 324 | return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * flexcan_get_bitrate() - Calculate CAN bitrate (in Hz) |
| 329 | * @s: FlexCAN device pointer |
| 330 | * |
| 331 | * The bitrate is determined by FlexCAN configuration in CTRL1 register, |
| 332 | * and CCM co |
| 333 | */ |
| 334 | static uint32_t flexcan_get_bitrate(FlexcanState *s) |
| 335 | { |
| 336 | uint32_t conf_presdiv = (s->regs.ctrl & FLEXCAN_CTRL_PRESDIV_MASK) >> 24; |
| 337 | uint32_t conf_pseg1 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG1_MASK) >> 19; |
| 338 | uint32_t conf_pseg2 = (s->regs.ctrl & FLEXCAN_CTRL_PSEG2_MASK) >> 16; |
| 339 | uint32_t conf_propseg = s->regs.ctrl & FLEXCAN_CTRL_PROPSEG_MASK; |
| 340 | |
| 341 | /* N of time quanta for segments */ |
| 342 | uint32_t tseg1 = 2 + conf_pseg1 + conf_propseg; |
| 343 | uint32_t tseg2 = 1 + conf_pseg2; |
| 344 | uint32_t total_qpb = 1 + tseg1 + tseg2; |
| 345 | |
| 346 | uint32_t pe_freq, s_freq, bitrate; |
| 347 | |
| 348 | /* s_freq: CAN clock from CCM divided by the prescaler */ |
| 349 | pe_freq = imx_ccm_get_clock_frequency(s->ccm, CLK_CAN); |
| 350 | s_freq = pe_freq / (1 + conf_presdiv); |
| 351 | bitrate = s_freq / total_qpb; |
| 352 | |
| 353 | trace_flexcan_get_bitrate(DEVICE(s)->canonical_path, pe_freq, |
| 354 | 1 + conf_presdiv, s_freq, tseg1, tseg2, total_qpb, |
| 355 | bitrate); |
| 356 | return bitrate; |
| 357 | } |
| 358 | |
| 359 | /** |
| 360 | * int128_mul_6464() - Multiply two 64-bit integers into a 128-bit one |
| 361 | */ |
| 362 | static Int128 int128_muls_6464(int64_t ai, int64_t bi) |
| 363 | { |
| 364 | uint64_t l, h; |
| 365 | |
| 366 | muls64(&l, &h, ai, bi); |
| 367 | return int128_make128(l, h); |
| 368 | } |
| 369 | |
| 370 | /** |
| 371 | * flexcan_get_timestamp() - Get current value of the 16-bit free-running timer |
| 372 | * @s: FlexCAN device pointer |
| 373 | * @mk_unique: if true, make the timestamp unique by incrementing it if needed |
| 374 | */ |
| 375 | static uint32_t flexcan_get_timestamp(FlexcanState *s, bool mk_unique) |
| 376 | { |
| 377 | const Int128 nanoseconds_in_second = int128_makes64((int64_t)1e9); |
| 378 | Int128 ncycles, cycles128; |
| 379 | int64_t current_time, elapsed_time_ns; |
| 380 | uint64_t cycles; |
| 381 | uint32_t rv, shift = 0; |
| 382 | |
| 383 | if (s->timer_start == FLEXCAN_TIMER_STOPPED) { |
| 384 | /* timer is not running, return last value */ |
| 385 | trace_flexcan_get_timestamp(DEVICE(s)->canonical_path, -1, 0, 0, 0, |
| 386 | s->regs.timer); |
| 387 | return s->regs.timer; |
| 388 | } |
| 389 | |
| 390 | current_time = flexcan_get_time(); |
| 391 | elapsed_time_ns = current_time - s->timer_start; |
| 392 | if (elapsed_time_ns < 0) { |
| 393 | trace_flexcan_timer_overflow(DEVICE(s)->canonical_path, current_time, |
| 394 | s->timer_start, elapsed_time_ns); |
| 395 | return 0xFFFF; |
| 396 | } |
| 397 | |
| 398 | ncycles = int128_muls_6464(s->timer_freq, elapsed_time_ns); |
| 399 | cycles128 = int128_divs(ncycles, nanoseconds_in_second); |
| 400 | /* 64 bits hold for over 50k years at 10MHz */ |
| 401 | cycles = int128_getlo(cycles128); |
| 402 | |
| 403 | if (mk_unique && cycles <= s->last_rx_timer_cycles) { |
| 404 | shift = 1; |
| 405 | cycles = s->last_rx_timer_cycles + shift; |
| 406 | } |
| 407 | |
| 408 | s->last_rx_timer_cycles = cycles; |
| 409 | rv = (uint32_t)cycles & 0xFFFF; |
| 410 | |
| 411 | trace_flexcan_get_timestamp(DEVICE(s)->canonical_path, |
| 412 | elapsed_time_ns / (uint32_t)1e6, |
| 413 | s->timer_freq, cycles, shift, rv); |
| 414 | return rv; |
| 415 | } |
| 416 | |
| 417 | /** |
| 418 | * flexcan_timer_start() - Start the free-running timer |
| 419 | * @s: FlexCAN device pointer |
| 420 | * |
| 421 | * This should be called when the module leaves freeze mode. |
| 422 | */ |
| 423 | static void flexcan_timer_start(FlexcanState *s) |
| 424 | { |
| 425 | s->timer_freq = flexcan_get_bitrate(s); |
| 426 | s->timer_start = flexcan_get_time(); |
| 427 | s->last_rx_timer_cycles = 0; |
| 428 | |
| 429 | trace_flexcan_timer_start(DEVICE(s)->canonical_path, s->timer_freq, |
| 430 | s->regs.timer); |
| 431 | } |
| 432 | |
| 433 | /** |
| 434 | * flexcan_timer_stop() - Stop the free-running timer |
| 435 | * @s: FlexCAN device pointer |
| 436 | * |
| 437 | * This should be called when the module enters freeze mode. |
| 438 | * Stores the current timestamp in the TIMER register. |
| 439 | */ |
| 440 | static void flexcan_timer_stop(FlexcanState *s) |
| 441 | { |
| 442 | s->regs.timer = flexcan_get_timestamp(s, false); |
| 443 | s->timer_start = FLEXCAN_TIMER_STOPPED; |
| 444 | |
| 445 | trace_flexcan_timer_stop(DEVICE(s)->canonical_path, s->timer_freq, |
| 446 | s->regs.timer); |
| 447 | } |
| 448 | |
| 449 | /* ========== IRQ handling ========== */ |
| 450 | /** |
| 451 | * flexcan_irq_update() - Update qemu_irq line based on interrupt registers |
| 452 | * @s: FlexCAN device pointer |
| 453 | */ |
| 454 | static void flexcan_irq_update(FlexcanState *s) |
| 455 | { |
| 456 | uint32_t mb_irqs[2]; |
| 457 | int irq_pending; |
| 458 | /* these are all interrupt sources from FlexCAN */ |
| 459 | /* mailbox interrupt sources */ |
| 460 | mb_irqs[0] = s->regs.iflag1 & s->regs.imask1; |
| 461 | mb_irqs[1] = s->regs.iflag2 & s->regs.imask2; |
| 462 | |
| 463 | /** |
| 464 | * these interrupts aren't currently used and they can never be raised |
| 465 | * |
| 466 | * bool irq_wake_up = (s->regs.mcr & FLEXCAN_MCR_WAK_MSK) && |
| 467 | * (s->regs.ecr & FLEXCAN_ESR_WAK_INT); |
| 468 | * bool irq_bus_off = (s->regs.ctrl & FLEXCAN_CTRL_BOFF_MSK) && |
| 469 | * (s->regs.ecr & FLEXCAN_ESR_BOFF_INT); |
| 470 | * bool irq_error = (s->regs.ctrl & FLEXCAN_CTRL_ERR_MSK) && |
| 471 | * (s->regs.ecr & FLEXCAN_ESR_ERR_INT); |
| 472 | * bool irq_tx_warn = (s->regs.ctrl & FLEXCAN_CTRL_TWRN_MSK) && |
| 473 | * (s->regs.ecr & FLEXCAN_ESR_TWRN_INT); |
| 474 | * bool irq_rx_warn = (s->regs.ctrl & FLEXCAN_CTRL_RWRN_MSK) && |
| 475 | * (s->regs.ecr & FLEXCAN_ESR_RWRN_INT); |
| 476 | */ |
| 477 | |
| 478 | irq_pending = (mb_irqs[0] || mb_irqs[1]) ? 1 : 0; |
| 479 | trace_flexcan_irq_update(DEVICE(s)->canonical_path, mb_irqs[0], mb_irqs[1], |
| 480 | irq_pending); |
| 481 | |
| 482 | qemu_set_irq(s->irq, irq_pending); |
| 483 | } |
| 484 | |
| 485 | /** |
| 486 | * flexcan_irq_iflag_set() - Set IFLAG bit corresponding to MB mbidx |
| 487 | * @s: FlexCAN device pointer |
| 488 | * @mbidx: mailbox index |
| 489 | */ |
| 490 | static void flexcan_irq_iflag_set(FlexcanState *s, int mbidx) |
| 491 | { |
| 492 | if (mbidx < 32) { |
| 493 | s->regs.iflag1 |= BIT(mbidx); |
| 494 | } else { |
| 495 | s->regs.iflag2 |= BIT(mbidx - 32); |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | /** |
| 500 | * flexcan_irq_iflag_clear() - Clear IFLAG bit corresponding to MB mbidx |
| 501 | * @s: FlexCAN device pointer |
| 502 | * @mbidx: mailbox index |
| 503 | */ |
| 504 | static void flexcan_irq_iflag_clear(FlexcanState *s, int mbidx) |
| 505 | { |
| 506 | if (mbidx < 32) { |
| 507 | s->regs.iflag1 &= ~BIT(mbidx); |
| 508 | } else { |
| 509 | s->regs.iflag2 &= ~BIT(mbidx - 32); |
| 510 | } |
| 511 | } |
| 512 | |
| 513 | /* ========== RESET ========== */ |
| 514 | static void flexcan_reset_local_state(FlexcanState *s) |
| 515 | { |
| 516 | uint32_t *reset_mask = (uint32_t *)&flexcan_regs_reset_mask; |
| 517 | for (int i = 0; i < (sizeof(FlexcanRegs) / 4); i++) { |
| 518 | s->regs_raw[i] &= reset_mask[i]; |
| 519 | } |
| 520 | |
| 521 | s->regs.mcr |= 0x5980000F; |
| 522 | s->locked_mbidx = FLEXCAN_NO_MB_LOCKED; |
| 523 | s->smb_target_mbidx = FLEXCAN_SMB_EMPTY; |
| 524 | s->timer_start = FLEXCAN_TIMER_STOPPED; |
| 525 | |
| 526 | trace_flexcan_reset(DEVICE(s)->canonical_path); |
| 527 | } |
| 528 | |
| 529 | static void flexcan_reset_enter(Object *obj, ResetType type) |
| 530 | { |
| 531 | FlexcanState *s = CAN_FLEXCAN(obj); |
| 532 | |
| 533 | memset(&s->regs, 0, sizeof(s->regs)); |
| 534 | flexcan_reset_local_state(s); |
| 535 | } |
| 536 | |
| 537 | static void flexcan_reset_hold(Object *obj, ResetType type) |
| 538 | { |
| 539 | FlexcanState *s = CAN_FLEXCAN(obj); |
| 540 | |
| 541 | flexcan_irq_update(s); |
| 542 | } |
| 543 | |
| 544 | |
| 545 | /* ========== Operation mode control ========== */ |
| 546 | /** |
| 547 | * flexcan_update_esr() - Update ESR based on mode and CAN bus connection state |
| 548 | * @s: FlexCAN device pointer |
| 549 | */ |
| 550 | static void flexcan_update_esr(FlexcanState *s) |
| 551 | { |
| 552 | bool is_running = (s->regs.mcr & FLEXCAN_MCR_NOT_RDY) == 0; |
| 553 | /* potentially, there could be other influences on ESR[SYNCH] */ |
| 554 | |
| 555 | if (is_running && s->canbus) { |
| 556 | s->regs.esr |= FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE; |
| 557 | } else { |
| 558 | s->regs.esr &= ~(FLEXCAN_ESR_SYNCH | FLEXCAN_ESR_IDLE); |
| 559 | } |
| 560 | } |
| 561 | |
| 562 | /** |
| 563 | * flexcan_update_esr() - Process MCR write |
| 564 | * @s: FlexCAN device pointer |
| 565 | * @pv: previously set MCR value |
| 566 | * |
| 567 | * This function expects the new MCR value to be already written in s->regs.mcr. |
| 568 | */ |
| 569 | static void flexcan_set_mcr(FlexcanState *s, const uint32_t pv) |
| 570 | { |
| 571 | uint32_t cv = s->regs.mcr; |
| 572 | |
| 573 | /* -- module disable mode -- */ |
| 574 | if (!(pv & FLEXCAN_MCR_MDIS) && (cv & FLEXCAN_MCR_MDIS)) { |
| 575 | /* transition to Module Disable mode */ |
| 576 | cv |= FLEXCAN_MCR_LPM_ACK; |
| 577 | } else if ((pv & FLEXCAN_MCR_MDIS) && !(cv & FLEXCAN_MCR_MDIS)) { |
| 578 | /* transition from Module Disable mode */ |
| 579 | cv &= ~FLEXCAN_MCR_LPM_ACK; |
| 580 | } |
| 581 | |
| 582 | /* -- soft reset -- */ |
| 583 | if (!(cv & FLEXCAN_MCR_LPM_ACK) && (cv & FLEXCAN_MCR_SOFTRST)) { |
| 584 | if (s->regs.mcr & FLEXCAN_MCR_LPM_ACK) { |
| 585 | qemu_log_mask(LOG_GUEST_ERROR, |
| 586 | "%s: invalid soft reset request in low-power mode", |
| 587 | DEVICE(s)->canonical_path); |
| 588 | } |
| 589 | |
| 590 | flexcan_reset_local_state(s); |
| 591 | cv = s->regs.mcr; |
| 592 | } |
| 593 | |
| 594 | /* -- freeze mode -- */ |
| 595 | if (!(cv & FLEXCAN_MCR_LPM_ACK) && |
| 596 | (cv & FLEXCAN_MCR_FRZ) && |
| 597 | (cv & FLEXCAN_MCR_HALT)) { |
| 598 | cv |= FLEXCAN_MCR_FRZ_ACK; |
| 599 | } else { |
| 600 | cv &= ~FLEXCAN_MCR_FRZ_ACK; |
| 601 | } |
| 602 | |
| 603 | /* -- fifo mode -- */ |
| 604 | if ( |
| 605 | ((pv & FLEXCAN_MCR_FEN) && !(cv & FLEXCAN_MCR_FEN)) || |
| 606 | (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) |
| 607 | ) { |
| 608 | /* clear iflags used by fifo */ |
| 609 | s->regs.iflag1 &= ~( |
| 610 | FLEXCAN_IFLAG_RX_FIFO_AVAILABLE | |
| 611 | FLEXCAN_IFLAG_RX_FIFO_OVERFLOW | |
| 612 | FLEXCAN_IFLAG_RX_FIFO_WARN |
| 613 | ); |
| 614 | } |
| 615 | if (!(pv & FLEXCAN_MCR_FEN) && (cv & FLEXCAN_MCR_FEN)) { |
| 616 | /* zero out fifo region, we rely on zeroed can_ctrl for empty slots */ |
| 617 | memset(s->regs.mbs, 0, |
| 618 | FLEXCAN_FIFO_DEPTH * sizeof(FlexcanRegsMessageBuffer)); |
| 619 | } |
| 620 | |
| 621 | /* |
| 622 | * assert NOT_RDY bit if in disable, |
| 623 | * stop (not implemented) or freeze mode |
| 624 | */ |
| 625 | if ((cv & FLEXCAN_MCR_LPM_ACK) || (cv & FLEXCAN_MCR_FRZ_ACK)) { |
| 626 | cv |= FLEXCAN_MCR_NOT_RDY; |
| 627 | } else { |
| 628 | cv &= ~FLEXCAN_MCR_NOT_RDY; |
| 629 | } |
| 630 | |
| 631 | if ((pv & FLEXCAN_MCR_NOT_RDY) && !(cv & FLEXCAN_MCR_NOT_RDY)) { |
| 632 | /* module went up, start the timer */ |
| 633 | flexcan_timer_start(s); |
| 634 | } else if (!(pv & FLEXCAN_MCR_NOT_RDY) && (cv & FLEXCAN_MCR_NOT_RDY)) { |
| 635 | /* module went down, store the current timer value */ |
| 636 | flexcan_timer_stop(s); |
| 637 | } |
| 638 | |
| 639 | s->regs.mcr = cv; |
| 640 | flexcan_update_esr(s); |
| 641 | trace_flexcan_set_mcr( |
| 642 | DEVICE(s)->canonical_path, |
| 643 | cv & FLEXCAN_MCR_LPM_ACK ? "DISABLED" : "ENABLED", |
| 644 | (cv & FLEXCAN_MCR_FRZ_ACK || cv & FLEXCAN_MCR_LPM_ACK) ? |
| 645 | "FROZEN" : "RUNNING", |
| 646 | cv & FLEXCAN_MCR_FEN ? "FIFO" : "MAILBOX", |
| 647 | cv & FLEXCAN_MCR_NOT_RDY ? "NOT_RDY" : "RDY", |
| 648 | s->regs.esr & FLEXCAN_ESR_SYNCH ? "SYNC" : "NOSYNC" |
| 649 | ); |
| 650 | } |
| 651 | |
| 652 | /* ========== TX ========== */ |
| 653 | static void flexcan_transmit(FlexcanState *s, int mbidx) |
| 654 | { |
| 655 | FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx]; |
| 656 | qemu_can_frame frame = { |
| 657 | .flags = 0, |
| 658 | }; |
| 659 | uint32_t *frame_data = (uint32_t *)&frame.data; |
| 660 | uint32_t timestamp = flexcan_get_timestamp(s, true); |
| 661 | |
| 662 | if ((s->regs.ctrl & FLEXCAN_CTRL_LOM) || |
| 663 | (s->regs.mcr & FLEXCAN_MCR_NOT_RDY)) { |
| 664 | /* no transmiting in listen-only, freeze or low-power mode */ |
| 665 | return; |
| 666 | } |
| 667 | |
| 668 | if (mb->can_ctrl & FLEXCAN_MB_CNT_IDE) { |
| 669 | /* 29b ID stored in bits [0, 29) */ |
| 670 | uint32_t id = mb->can_id & 0x1FFFFFFF; |
| 671 | frame.can_id = id | QEMU_CAN_EFF_FLAG; |
| 672 | } else { |
| 673 | /* 11b ID stored in bits [18, 29) */ |
| 674 | uint32_t id = (mb->can_id & (0x7FF << 18)) >> 18; |
| 675 | frame.can_id = id; |
| 676 | } |
| 677 | |
| 678 | frame.can_dlc = (mb->can_ctrl & (0xF << 16)) >> 16; |
| 679 | |
| 680 | for (int i = 0; i < 2; i++) { |
| 681 | stl_be_p(&frame_data[i], mb->data[i]); |
| 682 | } |
| 683 | |
| 684 | if (!(s->regs.mcr & FLEXCAN_MCR_SRX_DIS)) { |
| 685 | /* self-reception */ |
| 686 | flexcan_mb_rx(s, &frame); |
| 687 | } |
| 688 | if (!(s->regs.ctrl & FLEXCAN_CTRL_LPB)) { |
| 689 | /* send to bus if not in loopback mode */ |
| 690 | if (s->canbus) { |
| 691 | can_bus_client_send(&s->bus_client, &frame, 1); |
| 692 | } else { |
| 693 | /* todo: raise error (no ack) */ |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | mb->can_ctrl &= ~(FLEXCAN_MB_CODE_MASK | FLEXCAN_MB_CNT_TIMESTAMP_MASK); |
| 698 | mb->can_ctrl |= FLEXCAN_MB_CODE_TX_INACTIVE | |
| 699 | FLEXCAN_MB_CNT_TIMESTAMP(timestamp); |
| 700 | |
| 701 | /* todo: compute the CRC */ |
| 702 | s->regs.crcr = FLEXCAN_CRCR_TXCRC(0) | FLEXCAN_CRCR_MBCRC(mbidx); |
| 703 | |
| 704 | flexcan_irq_iflag_set(s, mbidx); |
| 705 | } |
| 706 | |
| 707 | static void flexcan_mb_write(FlexcanState *s, int mbid) |
| 708 | { |
| 709 | FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid]; |
| 710 | |
| 711 | bool is_mailbox = (mb <= flexcan_get_last_enabled_mailbox(s)) && |
| 712 | (mb >= flexcan_get_first_message_buffer(s)); |
| 713 | |
| 714 | if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_WRITE)) { |
| 715 | char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 }; |
| 716 | const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf); |
| 717 | trace_flexcan_mb_write(DEVICE(s)->canonical_path, mbid, code_str, |
| 718 | is_mailbox, mb->can_ctrl, mb->can_id); |
| 719 | } |
| 720 | |
| 721 | if (!is_mailbox) { |
| 722 | /** |
| 723 | * Disabled mailbox or mailbox in region of queue filters |
| 724 | * was updated. Either way there is nothing to do. |
| 725 | */ |
| 726 | return; |
| 727 | } |
| 728 | |
| 729 | /* any write to message buffer clears the not_serviced flag */ |
| 730 | mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV; |
| 731 | |
| 732 | /** |
| 733 | * todo: search for active tx mbs on transition from freeze/disable mode |
| 734 | */ |
| 735 | switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { |
| 736 | case FLEXCAN_MB_CODE_TX_INACTIVE: |
| 737 | QEMU_FALLTHROUGH; |
| 738 | case FLEXCAN_MB_CODE_RX_INACTIVE: |
| 739 | QEMU_FALLTHROUGH; |
| 740 | case FLEXCAN_MB_CODE_RX_EMPTY: |
| 741 | QEMU_FALLTHROUGH; |
| 742 | case FLEXCAN_MB_CODE_RX_FULL: |
| 743 | QEMU_FALLTHROUGH; |
| 744 | case FLEXCAN_MB_CODE_RX_RANSWER: |
| 745 | break; |
| 746 | |
| 747 | case FLEXCAN_MB_CODE_TX_DATA: |
| 748 | flexcan_transmit(s, mbid); |
| 749 | break; |
| 750 | case FLEXCAN_MB_CODE_TX_ABORT: |
| 751 | /* |
| 752 | * as transmission is instant, it can never be aborted |
| 753 | * we need to set CODE in C/S back to the previous code |
| 754 | */ |
| 755 | mb->can_ctrl &= ~FLEXCAN_MB_CODE(1); |
| 756 | break; |
| 757 | case FLEXCAN_MB_CODE_TX_TANSWER: |
| 758 | break; |
| 759 | default: |
| 760 | /* prevent setting the busy bit */ |
| 761 | mb->can_ctrl &= ~FLEXCAN_MB_CODE_RX_BUSY_BIT; |
| 762 | break; |
| 763 | } |
| 764 | |
| 765 | } |
| 766 | |
| 767 | /* ========== RX ========== */ |
| 768 | static void flexcan_mb_move_in(FlexcanState *s, const qemu_can_frame *frame, |
| 769 | FlexcanRegsMessageBuffer *target_mb) |
| 770 | { |
| 771 | uint32_t frame_len = frame->can_dlc; |
| 772 | uint32_t *frame_data = (uint32_t *)&frame->data; |
| 773 | int timestamp = flexcan_get_timestamp(s, true); |
| 774 | uint32_t new_code = 0; |
| 775 | |
| 776 | memset(target_mb, 0, sizeof(FlexcanRegsMessageBuffer)); |
| 777 | |
| 778 | if (frame_len > 8) { |
| 779 | frame_len = 8; |
| 780 | } |
| 781 | for (int i = 0; i < 2; i++) { |
| 782 | target_mb->data[i] = ldl_be_p(&frame_data[i]); |
| 783 | } |
| 784 | |
| 785 | switch (target_mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { |
| 786 | case FLEXCAN_MB_CODE_RX_FULL: |
| 787 | case FLEXCAN_MB_CODE_RX_OVERRUN: |
| 788 | if (target_mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV) { |
| 789 | new_code = FLEXCAN_MB_CODE_RX_OVERRUN; |
| 790 | } else { |
| 791 | new_code = FLEXCAN_MB_CODE_RX_FULL; |
| 792 | } |
| 793 | break; |
| 794 | case FLEXCAN_MB_CODE_RX_RANSWER: |
| 795 | assert(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS); |
| 796 | new_code = FLEXCAN_MB_CODE_TX_TANSWER; |
| 797 | break; |
| 798 | default: |
| 799 | new_code = FLEXCAN_MB_CODE_RX_FULL; |
| 800 | } |
| 801 | |
| 802 | target_mb->can_ctrl = new_code |
| 803 | | FLEXCAN_MB_CNT_TIMESTAMP(timestamp) |
| 804 | | FLEXCAN_MB_CNT_LENGTH(frame_len) |
| 805 | | FLEXCAN_MB_CNT_NOT_SRV |
| 806 | | FLEXCAN_MB_CNT_SRR; /* always set for received frames */ |
| 807 | if (frame->can_id & QEMU_CAN_RTR_FLAG) { |
| 808 | target_mb->can_ctrl |= FLEXCAN_MB_CNT_RTR; |
| 809 | } |
| 810 | |
| 811 | if (frame->can_id & QEMU_CAN_EFF_FLAG) { |
| 812 | target_mb->can_ctrl |= FLEXCAN_MB_CNT_IDE; |
| 813 | target_mb->can_id |= frame->can_id & QEMU_CAN_EFF_MASK; |
| 814 | } else { |
| 815 | target_mb->can_id |= (frame->can_id & QEMU_CAN_SFF_MASK) << 18; |
| 816 | } |
| 817 | } |
| 818 | static void flexcan_mb_lock(FlexcanState *s, int mbidx) |
| 819 | { |
| 820 | FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbidx]; |
| 821 | if ((mb > flexcan_get_last_enabled_mailbox(s)) || |
| 822 | (mb < flexcan_get_first_message_buffer(s))) { |
| 823 | return; |
| 824 | } |
| 825 | switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { |
| 826 | case FLEXCAN_MB_CODE_RX_FULL: |
| 827 | QEMU_FALLTHROUGH; |
| 828 | case FLEXCAN_MB_CODE_RX_OVERRUN: |
| 829 | QEMU_FALLTHROUGH; |
| 830 | case FLEXCAN_MB_CODE_RX_RANSWER: |
| 831 | /* continue */ |
| 832 | trace_flexcan_mb_lock(DEVICE(s)->canonical_path, mbidx, 1); |
| 833 | break; |
| 834 | default: |
| 835 | trace_flexcan_mb_lock(DEVICE(s)->canonical_path, mbidx, 0); |
| 836 | return; |
| 837 | } |
| 838 | |
| 839 | s->locked_mbidx = mbidx; |
| 840 | } |
| 841 | |
| 842 | static void flexcan_mb_unlock(FlexcanState *s) |
| 843 | { |
| 844 | int locked_mbidx = s->locked_mbidx; |
| 845 | bool has_pending_frame = locked_mbidx == s->smb_target_mbidx; |
| 846 | |
| 847 | if (s->locked_mbidx == FLEXCAN_NO_MB_LOCKED) { |
| 848 | return; |
| 849 | } |
| 850 | |
| 851 | assert(locked_mbidx >= 0 && locked_mbidx < FLEXCAN_MAILBOX_COUNT); |
| 852 | FlexcanRegsMessageBuffer *locked_mb = &s->regs.mbs[locked_mbidx]; |
| 853 | s->locked_mbidx = FLEXCAN_NO_MB_LOCKED; |
| 854 | |
| 855 | if (locked_mb >= flexcan_get_first_message_buffer(s) && |
| 856 | locked_mb <= flexcan_get_last_enabled_mailbox(s) |
| 857 | ) { |
| 858 | /* mark the message buffer as serviced */ |
| 859 | locked_mb->can_ctrl &= ~FLEXCAN_MB_CNT_NOT_SRV; |
| 860 | } |
| 861 | |
| 862 | /* try move in from SMB */ |
| 863 | trace_flexcan_mb_unlock(DEVICE(s)->canonical_path, locked_mbidx, |
| 864 | has_pending_frame ? " PENDING FRAME IN SMB" : ""); |
| 865 | |
| 866 | /* todo: in low-power modes, this should be postponed until exit */ |
| 867 | if (has_pending_frame) { |
| 868 | FlexcanRegsMessageBuffer *target_mb = &s->regs.mbs[locked_mbidx]; |
| 869 | memcpy(target_mb, &s->regs.rx_smb0, sizeof(FlexcanRegsMessageBuffer)); |
| 870 | |
| 871 | memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer)); |
| 872 | s->locked_mbidx = FLEXCAN_SMB_EMPTY; |
| 873 | |
| 874 | flexcan_irq_iflag_set(s, locked_mbidx); |
| 875 | } |
| 876 | } |
| 877 | |
| 878 | static bool flexcan_can_receive(CanBusClientState *client) |
| 879 | { |
| 880 | FlexcanState *s = container_of(client, FlexcanState, bus_client); |
| 881 | return !(s->regs.mcr & FLEXCAN_MCR_NOT_RDY); |
| 882 | } |
| 883 | |
| 884 | /* --------- RX FIFO ---------- */ |
| 885 | |
| 886 | /** |
| 887 | * flexcan_fifo_pop() - Pop message from FIFO and update IRQs |
| 888 | * @s: FlexCAN device pointer |
| 889 | * |
| 890 | * Does not require the queue to be non-empty. |
| 891 | */ |
| 892 | static void flexcan_fifo_pop(FlexcanState *s) |
| 893 | { |
| 894 | if (s->regs.mbs[0].can_ctrl != 0) { |
| 895 | /* move queue elements forward */ |
| 896 | memmove(&s->regs.mbs[0], &s->regs.mbs[1], |
| 897 | sizeof(s->regs.mbs[0]) * (FLEXCAN_FIFO_DEPTH - 1)); |
| 898 | |
| 899 | /* clear the first-in slot */ |
| 900 | memset(&s->regs.mbs[FLEXCAN_FIFO_DEPTH - 1], 0, |
| 901 | sizeof(FlexcanRegsMessageBuffer)); |
| 902 | |
| 903 | trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 1, |
| 904 | s->regs.mbs[0].can_ctrl != 0); |
| 905 | } else { |
| 906 | trace_flexcan_fifo_pop(DEVICE(s)->canonical_path, 0, 0); |
| 907 | } |
| 908 | |
| 909 | if (s->regs.mbs[0].can_ctrl != 0) { |
| 910 | flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE); |
| 911 | } else { |
| 912 | flexcan_irq_iflag_clear(s, I_FIFO_AVAILABLE); |
| 913 | } |
| 914 | } |
| 915 | |
| 916 | /** |
| 917 | * flexcan_fifo_find_free_slot() - Find the first free slot in the FIFO |
| 918 | * @s: FlexCAN device pointer |
| 919 | * |
| 920 | * Return: Pointer to the first free slot in the FIFO, |
| 921 | * or NULL if the queue is full. |
| 922 | */ |
| 923 | static FlexcanRegsMessageBuffer *flexcan_fifo_find_free_slot(FlexcanState *s) |
| 924 | { |
| 925 | for (int i = 0; i < FLEXCAN_FIFO_DEPTH; i++) { |
| 926 | FlexcanRegsMessageBuffer *mb = &s->regs.mbs[i]; |
| 927 | if (mb->can_ctrl == 0) { |
| 928 | return mb; |
| 929 | } |
| 930 | } |
| 931 | return NULL; |
| 932 | } |
| 933 | |
| 934 | /** |
| 935 | * flexcan_fifo_push() - Update FIFO IRQs after frame move-in |
| 936 | * @s: FlexCAN device pointer |
| 937 | * @slot: Target FIFO slot |
| 938 | * |
| 939 | * The usage is as follows: |
| 940 | * 1. Get free slot pointer using flexcan_fifo_find_free_slot() |
| 941 | * 2. Move the frame in if not NULL |
| 942 | * 3. Call flexcan_fifo_push() regardless of the NULL pointer |
| 943 | */ |
| 944 | static void flexcan_fifo_push(FlexcanState *s, FlexcanRegsMessageBuffer *slot) |
| 945 | { |
| 946 | if (slot) { |
| 947 | int n_occupied = slot - s->regs.mbs; |
| 948 | if (n_occupied == 4) { /* 4 means the 5th slot was filled in */ |
| 949 | /* |
| 950 | * fifo occupancy increased from 4 to 5, |
| 951 | * raising FIFO_WARN interrupt |
| 952 | */ |
| 953 | flexcan_irq_iflag_set(s, I_FIFO_WARN); |
| 954 | } |
| 955 | flexcan_irq_iflag_set(s, I_FIFO_AVAILABLE); |
| 956 | |
| 957 | trace_flexcan_fifo_push(DEVICE(s)->canonical_path, n_occupied); |
| 958 | } else { |
| 959 | flexcan_irq_iflag_set(s, I_FIFO_OVERFLOW); |
| 960 | |
| 961 | trace_flexcan_fifo_push(DEVICE(s)->canonical_path, -1); |
| 962 | } |
| 963 | } |
| 964 | |
| 965 | static enum FlexcanRx flexcan_fifo_rx(FlexcanState *s, |
| 966 | const qemu_can_frame *buf) |
| 967 | { |
| 968 | /* todo: filtering. return FLEXCAN_FIFO_RX_RETRY if filtered out */ |
| 969 | if ((s->regs.mcr & FLEXCAN_MCR_IDAM_MASK) == FLEXCAN_MCR_IDAM_D) { |
| 970 | /* all frames rejected */ |
| 971 | return FLEXCAN_RX_SEARCH_RETRY; |
| 972 | } else { |
| 973 | /* push message to queue if not full */ |
| 974 | FlexcanRegsMessageBuffer *slot = flexcan_fifo_find_free_slot(s); |
| 975 | if (slot) { |
| 976 | flexcan_mb_move_in(s, buf, slot); |
| 977 | } |
| 978 | flexcan_fifo_push(s, slot); |
| 979 | |
| 980 | return slot ? FLEXCAN_RX_SEARCH_ACCEPT : FLEXCAN_RX_SEARCH_DROPPED; |
| 981 | } |
| 982 | } |
| 983 | |
| 984 | /* --------- RX message buffer ---------- */ |
| 985 | |
| 986 | /** |
| 987 | * flexcan_mb_rx_check_mb() - Check if a mb matches a received frame |
| 988 | * @s: FlexCAN device pointer |
| 989 | * @buf: Frame to be received from CAN subsystem |
| 990 | * @mbid: Target mailbox index. The mailbox must be a valid message buffer. |
| 991 | * |
| 992 | * Return: FLEXCAN_CHECK_MB_NIL if the message buffer does not match. |
| 993 | * FLEXCAN_CHECK_MB_MATCH if the message buffer matches the received |
| 994 | * frame and is free-to-receive, |
| 995 | * FLEXCAN_CHECK_MB_MATCH_LOCKED if the message buffer matches, |
| 996 | * but is locked, |
| 997 | * FLEXCAN_CHECK_MB_MATCH_NON_FREE if the message buffer matches, |
| 998 | * but is not free-to-receive |
| 999 | * for some other reason. |
| 1000 | */ |
| 1001 | static enum FlexcanCheck flexcan_mb_rx_check_mb(FlexcanState *s, |
| 1002 | const qemu_can_frame *buf, |
| 1003 | int mbid) |
| 1004 | { |
| 1005 | FlexcanRegsMessageBuffer *mb = &s->regs.mbs[mbid]; |
| 1006 | const bool is_rtr = !!(buf->can_id & QEMU_CAN_RTR_FLAG); |
| 1007 | const bool is_serviced = !(mb->can_ctrl & FLEXCAN_MB_CNT_NOT_SRV); |
| 1008 | const bool is_locked = s->locked_mbidx == mbid; |
| 1009 | |
| 1010 | bool is_free_to_receive = false; |
| 1011 | bool is_matched = false; |
| 1012 | |
| 1013 | switch (mb->can_ctrl & FLEXCAN_MB_CODE_MASK) { |
| 1014 | case FLEXCAN_MB_CODE_RX_RANSWER: |
| 1015 | if (is_rtr && !(s->regs.ctrl2 & FLEXCAN_CTRL2_RRS)) { |
| 1016 | /* todo: do the actual matching/filtering and RTR answer */ |
| 1017 | is_matched = true; |
| 1018 | } |
| 1019 | break; |
| 1020 | case FLEXCAN_MB_CODE_RX_FULL: |
| 1021 | QEMU_FALLTHROUGH; |
| 1022 | case FLEXCAN_MB_CODE_RX_OVERRUN: |
| 1023 | is_free_to_receive = is_serviced; |
| 1024 | /* todo: do the actual matching/filtering */ |
| 1025 | is_matched = true; |
| 1026 | break; |
| 1027 | case FLEXCAN_MB_CODE_RX_EMPTY: |
| 1028 | is_free_to_receive = true; |
| 1029 | /* todo: do the actual matching/filtering */ |
| 1030 | is_matched = true; |
| 1031 | break; |
| 1032 | default: |
| 1033 | break; |
| 1034 | } |
| 1035 | |
| 1036 | if (trace_event_get_state_backends(TRACE_FLEXCAN_MB_RX_CHECK_MB)) { |
| 1037 | char code_str_buf[FLEXCAN_DBG_BUF_LEN] = { 0 }; |
| 1038 | const char *code_str = flexcan_dbg_mb_code(mb->can_ctrl, code_str_buf); |
| 1039 | trace_flexcan_mb_rx_check_mb(DEVICE(s)->canonical_path, mbid, code_str, |
| 1040 | is_matched, is_free_to_receive, |
| 1041 | is_serviced, is_locked); |
| 1042 | } |
| 1043 | |
| 1044 | if (!is_matched) { |
| 1045 | return FLEXCAN_CHECK_MB_NIL; |
| 1046 | } |
| 1047 | |
| 1048 | if (is_locked) { |
| 1049 | return FLEXCAN_CHECK_MB_MATCH_LOCKED; |
| 1050 | } |
| 1051 | |
| 1052 | if (is_free_to_receive) { |
| 1053 | return FLEXCAN_CHECK_MB_MATCH; |
| 1054 | } |
| 1055 | |
| 1056 | return FLEXCAN_CHECK_MB_MATCH_NON_FREE; |
| 1057 | } |
| 1058 | |
| 1059 | static enum FlexcanRx flexcan_mb_rx(FlexcanState *s, const qemu_can_frame *buf) |
| 1060 | { |
| 1061 | int last_not_free_to_receive_mbid = -1; |
| 1062 | bool last_not_free_to_receive_locked = false; |
| 1063 | |
| 1064 | FlexcanRegsMessageBuffer *first_mb = flexcan_get_first_message_buffer(s); |
| 1065 | FlexcanRegsMessageBuffer *last_mb = flexcan_get_last_enabled_mailbox(s); |
| 1066 | |
| 1067 | for (FlexcanRegsMessageBuffer *mb = first_mb; |
| 1068 | mb <= last_mb; mb++) { |
| 1069 | int mbid = mb - s->regs.mbs; |
| 1070 | enum FlexcanCheck r = flexcan_mb_rx_check_mb(s, buf, mbid); |
| 1071 | if (r == FLEXCAN_CHECK_MB_MATCH) { |
| 1072 | flexcan_mb_move_in(s, buf, mb); |
| 1073 | flexcan_irq_iflag_set(s, mbid); |
| 1074 | return FLEXCAN_RX_SEARCH_ACCEPT; |
| 1075 | } |
| 1076 | |
| 1077 | if (r == FLEXCAN_CHECK_MB_MATCH_NON_FREE) { |
| 1078 | last_not_free_to_receive_mbid = mbid; |
| 1079 | last_not_free_to_receive_locked = false; |
| 1080 | } else if (r == FLEXCAN_CHECK_MB_MATCH_LOCKED) { |
| 1081 | /* |
| 1082 | * message buffer is locked, |
| 1083 | * we can move in the message after it's unlocked |
| 1084 | */ |
| 1085 | last_not_free_to_receive_mbid = mbid; |
| 1086 | last_not_free_to_receive_locked = true; |
| 1087 | } |
| 1088 | } |
| 1089 | |
| 1090 | if (last_not_free_to_receive_mbid >= 0) { |
| 1091 | if (last_not_free_to_receive_locked) { |
| 1092 | /* |
| 1093 | * copy to temporary mailbox (SMB) |
| 1094 | * it will be moved in when the mailbox is unlocked |
| 1095 | */ |
| 1096 | s->regs.rx_smb0.can_ctrl = |
| 1097 | s->regs.mbs[last_not_free_to_receive_mbid].can_id; |
| 1098 | flexcan_mb_move_in(s, buf, &s->regs.rx_smb0); |
| 1099 | s->smb_target_mbidx = last_not_free_to_receive_mbid; |
| 1100 | return FLEXCAN_RX_SEARCH_ACCEPT; |
| 1101 | } |
| 1102 | |
| 1103 | if (s->regs.mcr & FLEXCAN_MCR_IRMQ) { |
| 1104 | flexcan_mb_move_in(s, buf, |
| 1105 | &s->regs.mbs[last_not_free_to_receive_mbid]); |
| 1106 | flexcan_irq_iflag_set(s, last_not_free_to_receive_mbid); |
| 1107 | return FLEXCAN_RX_SEARCH_ACCEPT; |
| 1108 | } |
| 1109 | } |
| 1110 | |
| 1111 | return FLEXCAN_RX_SEARCH_RETRY; |
| 1112 | } |
| 1113 | |
| 1114 | static ssize_t flexcan_receive(CanBusClientState *client, |
| 1115 | const qemu_can_frame *frames, size_t frames_cnt) |
| 1116 | { |
| 1117 | FlexcanState *s = container_of(client, FlexcanState, bus_client); |
| 1118 | trace_flexcan_receive(DEVICE(s)->canonical_path, frames_cnt); |
| 1119 | |
| 1120 | if (frames_cnt == 0) { |
| 1121 | return 0; |
| 1122 | } |
| 1123 | |
| 1124 | /* clear the SMB, as it would be overriden in hardware */ |
| 1125 | memset(&s->regs.rx_smb0, 0, sizeof(FlexcanRegsMessageBuffer)); |
| 1126 | s->smb_target_mbidx = FLEXCAN_SMB_EMPTY; |
| 1127 | |
| 1128 | for (size_t i = 0; i < frames_cnt; i++) { |
| 1129 | int r; |
| 1130 | const qemu_can_frame *frame = &frames[i]; |
| 1131 | if (frame->can_id & QEMU_CAN_ERR_FLAG) { |
| 1132 | /* todo: error frame handling */ |
| 1133 | continue; |
| 1134 | } |
| 1135 | if (frame->flags & QEMU_CAN_FRMF_TYPE_FD) { |
| 1136 | /* CAN FD supported only in later FlexCAN version */ |
| 1137 | continue; |
| 1138 | } |
| 1139 | |
| 1140 | /* todo: this order logic is not complete and needs further work */ |
| 1141 | if (s->regs.mcr & FLEXCAN_MCR_FEN && |
| 1142 | s->regs.ctrl2 & FLEXCAN_CTRL2_MRP) { |
| 1143 | r = flexcan_mb_rx(s, frame); |
| 1144 | if (r == FLEXCAN_RX_SEARCH_RETRY) { |
| 1145 | flexcan_fifo_rx(s, frame); |
| 1146 | } |
| 1147 | } else if (s->regs.mcr & FLEXCAN_MCR_FEN) { |
| 1148 | r = flexcan_fifo_rx(s, frame); |
| 1149 | if (r == FLEXCAN_RX_SEARCH_RETRY) { |
| 1150 | flexcan_mb_rx(s, frame); |
| 1151 | } |
| 1152 | } else { |
| 1153 | flexcan_mb_rx(s, frame); |
| 1154 | } |
| 1155 | } |
| 1156 | |
| 1157 | flexcan_irq_update(s); |
| 1158 | return 1; |
| 1159 | } |
| 1160 | |
| 1161 | /* ========== I/O handling ========== */ |
| 1162 | static void flexcan_mem_write(void *opaque, hwaddr addr, uint64_t val, |
| 1163 | unsigned size) |
| 1164 | { |
| 1165 | FlexcanState *s = opaque; |
| 1166 | const int mbid = (addr - offsetof(FlexcanRegs, mbs)) / |
| 1167 | sizeof(s->regs.mbs[0]); |
| 1168 | uint32_t write_mask = ((const uint32_t *) |
| 1169 | &flexcan_regs_write_mask)[addr / 4]; |
| 1170 | uint32_t old_value = s->regs_raw[addr / 4]; |
| 1171 | |
| 1172 | /* |
| 1173 | * 0 for bits that can "only be written in Freeze mode as it is blocked |
| 1174 | * by hardware in other modes" |
| 1175 | */ |
| 1176 | const uint32_t freeze_mask_mcr = 0xDF54CC80; |
| 1177 | const uint32_t freeze_mask_ctrl1 = 0x0000E740; |
| 1178 | |
| 1179 | flexcan_trace_mem_op(s, addr, val, size, true); |
| 1180 | switch (addr) { |
| 1181 | case offsetof(FlexcanRegs, mcr): |
| 1182 | if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { |
| 1183 | write_mask &= freeze_mask_mcr; |
| 1184 | } |
| 1185 | s->regs.mcr = (val & write_mask) | (old_value & ~write_mask); |
| 1186 | flexcan_set_mcr(s, old_value); |
| 1187 | break; |
| 1188 | case offsetof(FlexcanRegs, ctrl): |
| 1189 | if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { |
| 1190 | write_mask &= freeze_mask_ctrl1; |
| 1191 | } |
| 1192 | s->regs.ctrl = (val & write_mask) | (old_value & ~write_mask); |
| 1193 | break; |
| 1194 | case offsetof(FlexcanRegs, iflag1): |
| 1195 | s->regs.iflag1 &= ~val; |
| 1196 | if ((s->regs.mcr & FLEXCAN_MCR_FEN) && |
| 1197 | (val & FLEXCAN_IFLAG_RX_FIFO_AVAILABLE)) { |
| 1198 | flexcan_fifo_pop(s); |
| 1199 | } |
| 1200 | break; |
| 1201 | case offsetof(FlexcanRegs, iflag2): |
| 1202 | s->regs.iflag2 &= ~val; |
| 1203 | break; |
| 1204 | case offsetof(FlexcanRegs, ctrl2): |
| 1205 | QEMU_FALLTHROUGH; |
| 1206 | case offsetof(FlexcanRegs, ecr): |
| 1207 | QEMU_FALLTHROUGH; |
| 1208 | case offsetof(FlexcanRegs, rxmgmask): |
| 1209 | QEMU_FALLTHROUGH; |
| 1210 | case offsetof(FlexcanRegs, rx14mask): |
| 1211 | QEMU_FALLTHROUGH; |
| 1212 | case offsetof(FlexcanRegs, rx15mask): |
| 1213 | QEMU_FALLTHROUGH; |
| 1214 | case offsetof(FlexcanRegs, rxfgmask): |
| 1215 | QEMU_FALLTHROUGH; |
| 1216 | case offsetof(FlexcanRegs, rximr[0]) ... offsetof(FlexcanRegs, rximr[63]): |
| 1217 | /* these registers can only be written in freeze mode */ |
| 1218 | if (!(s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { |
| 1219 | break; |
| 1220 | } |
| 1221 | QEMU_FALLTHROUGH; |
| 1222 | default: |
| 1223 | s->regs_raw[addr / 4] = (val & write_mask) | (old_value & ~write_mask); |
| 1224 | |
| 1225 | if (0 <= mbid && mbid < ARRAY_SIZE(s->regs.mbs)) { |
| 1226 | /* access to mailbox */ |
| 1227 | |
| 1228 | if (s->locked_mbidx == mbid) { |
| 1229 | flexcan_mb_unlock(s); |
| 1230 | } |
| 1231 | |
| 1232 | /* check for invalid writes into FIFO region */ |
| 1233 | if (s->regs.mcr & FLEXCAN_MCR_FEN && mbid < FLEXCAN_FIFO_DEPTH) { |
| 1234 | qemu_log_mask(LOG_GUEST_ERROR, |
| 1235 | "%s: Invalid write to Rx-FIFO structure", |
| 1236 | DEVICE(s)->canonical_path); |
| 1237 | return; |
| 1238 | } |
| 1239 | |
| 1240 | /* run mailbox processing function on write to control word */ |
| 1241 | if ((addr & 0xF) == 0) { |
| 1242 | flexcan_mb_write(s, mbid); |
| 1243 | } |
| 1244 | } |
| 1245 | break; |
| 1246 | } |
| 1247 | |
| 1248 | flexcan_irq_update(s); |
| 1249 | } |
| 1250 | |
| 1251 | static uint64_t flexcan_mem_read(void *opqaue, hwaddr addr, unsigned size) |
| 1252 | { |
| 1253 | FlexcanState *s = opqaue; |
| 1254 | const int mbid = (addr - offsetof(FlexcanRegs, mbs)) / |
| 1255 | sizeof(s->regs.mbs[0]); |
| 1256 | uint32_t rv = s->regs_raw[addr >> 2]; |
| 1257 | |
| 1258 | if (0 <= mbid && mbid < ARRAY_SIZE(s->regs.mbs)) { |
| 1259 | /* reading from mailbox */ |
| 1260 | if (addr % 16 == 0 && s->locked_mbidx != mbid) { |
| 1261 | /* reading control word locks the mailbox */ |
| 1262 | flexcan_mb_unlock(s); |
| 1263 | flexcan_mb_lock(s, mbid); |
| 1264 | flexcan_irq_update(s); |
| 1265 | rv = s->regs.mbs[mbid].can_ctrl & ~FLEXCAN_MB_CNT_NOT_SRV; |
| 1266 | } |
| 1267 | } else if (addr == offsetof(FlexcanRegs, timer)) { |
| 1268 | flexcan_mb_unlock(s); |
| 1269 | flexcan_irq_update(s); |
| 1270 | rv = flexcan_get_timestamp(s, false); |
| 1271 | } |
| 1272 | |
| 1273 | flexcan_trace_mem_op(s, addr, rv, size, false); |
| 1274 | return rv; |
| 1275 | } |
| 1276 | |
| 1277 | static bool flexcan_mem_accepts(void *opaque, hwaddr addr, |
| 1278 | unsigned size, bool is_write, |
| 1279 | MemTxAttrs attrs) |
| 1280 | { |
| 1281 | FlexcanState *s = opaque; |
| 1282 | |
| 1283 | if ((s->regs.ctrl2 & FLEXCAN_CTRL2_WRMFRZ) && |
| 1284 | (s->regs.mcr & FLEXCAN_MCR_FRZ_ACK)) { |
| 1285 | /* unrestricted access to FlexCAN memory in freeze mode */ |
| 1286 | return true; |
| 1287 | } else if (attrs.user && (s->regs.mcr & FLEXCAN_MCR_SUPV)) { |
| 1288 | qemu_log_mask(LOG_GUEST_ERROR, |
| 1289 | "%s: Invalid user-mode access to restricted register", |
| 1290 | DEVICE(s)->canonical_path); |
| 1291 | return false; |
| 1292 | } else if (attrs.user && is_write && addr < 4) { |
| 1293 | qemu_log_mask(LOG_GUEST_ERROR, |
| 1294 | "%s: Invalid user-mode access to MCR", |
| 1295 | DEVICE(s)->canonical_path); |
| 1296 | return false; |
| 1297 | } |
| 1298 | |
| 1299 | return true; |
| 1300 | } |
| 1301 | |
| 1302 | static const struct MemoryRegionOps flexcan2_ops = { |
| 1303 | .read = flexcan_mem_read, |
| 1304 | .write = flexcan_mem_write, |
| 1305 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 1306 | .valid = { |
| 1307 | .min_access_size = 1, |
| 1308 | .max_access_size = 4, |
| 1309 | .unaligned = true, |
| 1310 | .accepts = flexcan_mem_accepts |
| 1311 | }, |
| 1312 | .impl = { |
| 1313 | .min_access_size = 4, |
| 1314 | .max_access_size = 4, |
| 1315 | .unaligned = false |
| 1316 | }, |
| 1317 | }; |
| 1318 | |
| 1319 | static const struct MemoryRegionOps flexcan3_ops = { |
| 1320 | .read = flexcan_mem_read, |
| 1321 | .write = flexcan_mem_write, |
| 1322 | .endianness = DEVICE_LITTLE_ENDIAN, |
| 1323 | .valid = { |
| 1324 | .min_access_size = 1, |
| 1325 | .max_access_size = 8, |
| 1326 | .unaligned = true, |
| 1327 | .accepts = flexcan_mem_accepts |
| 1328 | }, |
| 1329 | .impl = { |
| 1330 | .min_access_size = 4, |
| 1331 | .max_access_size = 4, |
| 1332 | .unaligned = false |
| 1333 | }, |
| 1334 | }; |
| 1335 | |
| 1336 | static CanBusClientInfo flexcan_bus_client_info = { |
| 1337 | .can_receive = flexcan_can_receive, |
| 1338 | .receive = flexcan_receive, |
| 1339 | }; |
| 1340 | |
| 1341 | static int flexcan_connect_to_bus(FlexcanState *s, CanBusState *bus) |
| 1342 | { |
| 1343 | s->bus_client.info = &flexcan_bus_client_info; |
| 1344 | |
| 1345 | if (can_bus_insert_client(bus, &s->bus_client) < 0) { |
| 1346 | return -1; |
| 1347 | } |
| 1348 | return 0; |
| 1349 | } |
| 1350 | |
| 1351 | static void flexcan2_init(Object *obj) |
| 1352 | { |
| 1353 | FlexcanState *s = CAN_FLEXCAN(obj); |
| 1354 | |
| 1355 | memory_region_init_io( |
| 1356 | &s->iomem, obj, &flexcan2_ops, s, TYPE_CAN_FLEXCAN2, |
| 1357 | offsetof(FlexcanRegs, _reserved6) |
| 1358 | ); |
| 1359 | } |
| 1360 | |
| 1361 | static void flexcan3_init(Object *obj) |
| 1362 | { |
| 1363 | FlexcanState *s = CAN_FLEXCAN(obj); |
| 1364 | |
| 1365 | memory_region_init_io( |
| 1366 | &s->iomem, obj, &flexcan3_ops, s, TYPE_CAN_FLEXCAN3, |
| 1367 | sizeof(FlexcanRegs) |
| 1368 | ); |
| 1369 | } |
| 1370 | |
| 1371 | static void flexcan_realize(DeviceState *dev, Error **errp) |
| 1372 | { |
| 1373 | FlexcanState *s = CAN_FLEXCAN(dev); |
| 1374 | |
| 1375 | if (s->canbus) { |
| 1376 | if (flexcan_connect_to_bus(s, s->canbus) < 0) { |
| 1377 | error_setg(errp, "%s: flexcan_connect_to_bus failed", |
| 1378 | dev->canonical_path); |
| 1379 | return; |
| 1380 | } |
| 1381 | } |
| 1382 | |
| 1383 | if (!s->ccm) { |
| 1384 | error_setg(errp, "%s 'clock-control-module' link property not set", |
| 1385 | dev->canonical_path); |
| 1386 | return; |
| 1387 | } |
| 1388 | |
| 1389 | sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); |
| 1390 | sysbus_init_irq(SYS_BUS_DEVICE(SYS_BUS_DEVICE(dev)), &s->irq); |
| 1391 | } |
| 1392 | |
| 1393 | static const VMStateDescription vmstate_can = { |
| 1394 | .name = TYPE_CAN_FLEXCAN, |
| 1395 | .version_id = 1, |
| 1396 | .minimum_version_id = 1, |
| 1397 | .fields = (const VMStateField[]) { |
| 1398 | VMSTATE_INT64(timer_start, FlexcanState), |
| 1399 | VMSTATE_UINT32_ARRAY(regs_raw, FlexcanState, sizeof(FlexcanRegs) / 4), |
| 1400 | VMSTATE_INT32(locked_mbidx, FlexcanState), |
| 1401 | VMSTATE_INT32(smb_target_mbidx, FlexcanState), |
| 1402 | VMSTATE_END_OF_LIST(), |
| 1403 | }, |
| 1404 | }; |
| 1405 | |
| 1406 | static const Property flexcan_properties[] = { |
| 1407 | DEFINE_PROP_LINK("canbus", FlexcanState, canbus, TYPE_CAN_BUS, |
| 1408 | CanBusState *), |
| 1409 | DEFINE_PROP_LINK("clock-control-module", FlexcanState, ccm, TYPE_IMX_CCM, |
| 1410 | IMXCCMState *), |
| 1411 | }; |
| 1412 | |
| 1413 | static void flexcan_class_init(ObjectClass *klass, const void *data) |
| 1414 | { |
| 1415 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1416 | ResettableClass *rc = RESETTABLE_CLASS(klass); |
| 1417 | |
| 1418 | rc->phases.enter = flexcan_reset_enter; |
| 1419 | rc->phases.hold = flexcan_reset_hold; |
| 1420 | dc->realize = flexcan_realize; |
| 1421 | device_class_set_props(dc, flexcan_properties); |
| 1422 | dc->vmsd = &vmstate_can; |
| 1423 | } |
| 1424 | |
| 1425 | static void flexcan2_class_init(ObjectClass *klass, const void *data) |
| 1426 | { |
| 1427 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1428 | |
| 1429 | dc->desc = "i.MX FlexCAN 2 Controller"; |
| 1430 | } |
| 1431 | |
| 1432 | static void flexcan3_class_init(ObjectClass *klass, const void *data) |
| 1433 | { |
| 1434 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 1435 | |
| 1436 | dc->desc = "i.MX FlexCAN 3 Controller"; |
| 1437 | } |
| 1438 | |
| 1439 | static const TypeInfo flexcan_types[] = { |
| 1440 | { |
| 1441 | .name = TYPE_CAN_FLEXCAN, |
| 1442 | .parent = TYPE_SYS_BUS_DEVICE, |
| 1443 | .instance_size = sizeof(FlexcanState), |
| 1444 | .class_init = flexcan_class_init, |
| 1445 | .abstract = true, |
| 1446 | }, |
| 1447 | { |
| 1448 | .name = TYPE_CAN_FLEXCAN2, |
| 1449 | .parent = TYPE_CAN_FLEXCAN, |
| 1450 | .class_init = flexcan2_class_init, |
| 1451 | .instance_init = flexcan2_init, |
| 1452 | }, |
| 1453 | { |
| 1454 | .name = TYPE_CAN_FLEXCAN3, |
| 1455 | .parent = TYPE_CAN_FLEXCAN, |
| 1456 | .class_init = flexcan3_class_init, |
| 1457 | .instance_init = flexcan3_init, |
| 1458 | }, |
| 1459 | }; |
| 1460 | |
| 1461 | DEFINE_TYPES(flexcan_types) |