|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | // | 
|  | // mcp251xfd - Microchip MCP251xFD Family CAN controller driver | 
|  | // | 
|  | // Copyright (c) 2021, 2022 Pengutronix, | 
|  | //               Marc Kleine-Budde <kernel@pengutronix.de> | 
|  | // | 
|  |  | 
|  | #include "mcp251xfd-ram.h" | 
|  |  | 
|  | static inline u8 can_ram_clamp(const struct can_ram_config *config, | 
|  | const struct can_ram_obj_config *obj, | 
|  | u8 val) | 
|  | { | 
|  | u8 max; | 
|  |  | 
|  | max = min_t(u8, obj->max, obj->fifo_num * config->fifo_depth); | 
|  | return clamp(val, obj->min, max); | 
|  | } | 
|  |  | 
|  | static u8 | 
|  | can_ram_rounddown_pow_of_two(const struct can_ram_config *config, | 
|  | const struct can_ram_obj_config *obj, | 
|  | const u8 coalesce, u8 val) | 
|  | { | 
|  | u8 fifo_num = obj->fifo_num; | 
|  | u8 ret = 0, i; | 
|  |  | 
|  | val = can_ram_clamp(config, obj, val); | 
|  |  | 
|  | if (coalesce) { | 
|  | /* Use 1st FIFO for coalescing, if requested. | 
|  | * | 
|  | * Either use complete FIFO (and FIFO Full IRQ) for | 
|  | * coalescing or only half of FIFO (FIFO Half Full | 
|  | * IRQ) and use remaining half for normal objects. | 
|  | */ | 
|  | ret = min_t(u8, coalesce * 2, config->fifo_depth); | 
|  | val -= ret; | 
|  | fifo_num--; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < fifo_num && val; i++) { | 
|  | u8 n; | 
|  |  | 
|  | n = min_t(u8, rounddown_pow_of_two(val), | 
|  | config->fifo_depth); | 
|  |  | 
|  | /* skip small FIFOs */ | 
|  | if (n < obj->fifo_depth_min) | 
|  | return ret; | 
|  |  | 
|  | ret += n; | 
|  | val -= n; | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | void can_ram_get_layout(struct can_ram_layout *layout, | 
|  | const struct can_ram_config *config, | 
|  | const struct ethtool_ringparam *ring, | 
|  | const struct ethtool_coalesce *ec, | 
|  | const bool fd_mode) | 
|  | { | 
|  | u8 num_rx, num_tx; | 
|  | u16 ram_free; | 
|  |  | 
|  | /* default CAN */ | 
|  |  | 
|  | num_tx = config->tx.def[fd_mode]; | 
|  | num_tx = can_ram_rounddown_pow_of_two(config, &config->tx, 0, num_tx); | 
|  |  | 
|  | ram_free = config->size; | 
|  | ram_free -= config->tx.size[fd_mode] * num_tx; | 
|  |  | 
|  | num_rx = ram_free / config->rx.size[fd_mode]; | 
|  |  | 
|  | layout->default_rx = can_ram_rounddown_pow_of_two(config, &config->rx, 0, num_rx); | 
|  | layout->default_tx = num_tx; | 
|  |  | 
|  | /* MAX CAN */ | 
|  |  | 
|  | ram_free = config->size; | 
|  | ram_free -= config->tx.size[fd_mode] * config->tx.min; | 
|  | num_rx = ram_free / config->rx.size[fd_mode]; | 
|  |  | 
|  | ram_free = config->size; | 
|  | ram_free -= config->rx.size[fd_mode] * config->rx.min; | 
|  | num_tx = ram_free / config->tx.size[fd_mode]; | 
|  |  | 
|  | layout->max_rx = can_ram_rounddown_pow_of_two(config, &config->rx, 0, num_rx); | 
|  | layout->max_tx = can_ram_rounddown_pow_of_two(config, &config->tx, 0, num_tx); | 
|  |  | 
|  | /* cur CAN */ | 
|  |  | 
|  | if (ring) { | 
|  | u8 num_rx_coalesce = 0, num_tx_coalesce = 0; | 
|  |  | 
|  | /* If the ring parameters have been configured in | 
|  | * CAN-CC mode, but and we are in CAN-FD mode now, | 
|  | * they might be to big. Use the default CAN-FD values | 
|  | * in this case. | 
|  | */ | 
|  | num_rx = ring->rx_pending; | 
|  | if (num_rx > layout->max_rx) | 
|  | num_rx = layout->default_rx; | 
|  |  | 
|  | num_rx = can_ram_rounddown_pow_of_two(config, &config->rx, 0, num_rx); | 
|  |  | 
|  | /* The ethtool doc says: | 
|  | * To disable coalescing, set usecs = 0 and max_frames = 1. | 
|  | */ | 
|  | if (ec && !(ec->rx_coalesce_usecs_irq == 0 && | 
|  | ec->rx_max_coalesced_frames_irq == 1)) { | 
|  | u8 max; | 
|  |  | 
|  | /* use only max half of available objects for coalescing */ | 
|  | max = min_t(u8, num_rx / 2, config->fifo_depth); | 
|  | num_rx_coalesce = clamp(ec->rx_max_coalesced_frames_irq, | 
|  | (u32)config->rx.fifo_depth_coalesce_min, | 
|  | (u32)max); | 
|  | num_rx_coalesce = rounddown_pow_of_two(num_rx_coalesce); | 
|  |  | 
|  | num_rx = can_ram_rounddown_pow_of_two(config, &config->rx, | 
|  | num_rx_coalesce, num_rx); | 
|  | } | 
|  |  | 
|  | ram_free = config->size - config->rx.size[fd_mode] * num_rx; | 
|  | num_tx = ram_free / config->tx.size[fd_mode]; | 
|  | num_tx = min_t(u8, ring->tx_pending, num_tx); | 
|  | num_tx = can_ram_rounddown_pow_of_two(config, &config->tx, 0, num_tx); | 
|  |  | 
|  | /* The ethtool doc says: | 
|  | * To disable coalescing, set usecs = 0 and max_frames = 1. | 
|  | */ | 
|  | if (ec && !(ec->tx_coalesce_usecs_irq == 0 && | 
|  | ec->tx_max_coalesced_frames_irq == 1)) { | 
|  | u8 max; | 
|  |  | 
|  | /* use only max half of available objects for coalescing */ | 
|  | max = min_t(u8, num_tx / 2, config->fifo_depth); | 
|  | num_tx_coalesce = clamp(ec->tx_max_coalesced_frames_irq, | 
|  | (u32)config->tx.fifo_depth_coalesce_min, | 
|  | (u32)max); | 
|  | num_tx_coalesce = rounddown_pow_of_two(num_tx_coalesce); | 
|  |  | 
|  | num_tx = can_ram_rounddown_pow_of_two(config, &config->tx, | 
|  | num_tx_coalesce, num_tx); | 
|  | } | 
|  |  | 
|  | layout->cur_rx = num_rx; | 
|  | layout->cur_tx = num_tx; | 
|  | layout->rx_coalesce = num_rx_coalesce; | 
|  | layout->tx_coalesce = num_tx_coalesce; | 
|  | } else { | 
|  | layout->cur_rx = layout->default_rx; | 
|  | layout->cur_tx = layout->default_tx; | 
|  | layout->rx_coalesce = 0; | 
|  | layout->tx_coalesce = 0; | 
|  | } | 
|  | } |