// SPDX-License-Identifier: GPL-2.0
/*
* dim2.c - MediaLB DIM2 Hardware Dependent Module
*
* Copyright (C) 2015-2016, Microchip Technology Germany II GmbH & Co. KG
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/printk.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/most.h>
#include "hal.h"
#include "errors.h"
#include "sysfs.h"
#define DMA_CHANNELS (32 - 1) /* channel 0 is a system channel */
#define MAX_BUFFERS_PACKET 32
#define MAX_BUFFERS_STREAMING 32
#define MAX_BUF_SIZE_PACKET 2048
#define MAX_BUF_SIZE_STREAMING (8 * 1024)
/*
* The parameter representing the number of frames per sub-buffer for
* synchronous channels. Valid values: [0 .. 6].
*
* The values 0, 1, 2, 3, 4, 5, 6 represent corresponding number of frames per
* sub-buffer 1, 2, 4, 8, 16, 32, 64.
*/
static u8 fcnt = 4; /* (1 << fcnt) frames per subbuffer */
module_param(fcnt, byte, 0000);
MODULE_PARM_DESC(fcnt, "Num of frames per sub-buffer for sync channels as a power of 2");
static DEFINE_SPINLOCK(dim_lock);
static void dim2_tasklet_fn(unsigned long data);
static DECLARE_TASKLET_OLD(dim2_tasklet, dim2_tasklet_fn);
/**
* struct hdm_channel - private structure to keep channel specific data
* @is_initialized: identifier to know whether the channel is initialized
* @ch: HAL specific channel data
* @pending_list: list to keep MBO's before starting transfer
* @started_list: list to keep MBO's after starting transfer
* @direction: channel direction (TX or RX)
* @data_type: channel data type
*/
struct hdm_channel {
char name[sizeof "caNNN"];
bool is_initialized;
struct dim_channel ch;
u16 *reset_dbr_size;
struct list_head pending_list; /* before dim_enqueue_buffer() */
struct list_head started_list; /* after dim_enqueue_buffer() */
enum most_channel_direction direction;
enum most_channel_data_type data_type;
};
/**
* struct dim2_hdm - private structure to keep interface specific data
* @hch: an array of channel specific data
* @most_iface: most interface structure
* @capabilities: an array of channel capability data
* @io_base: I/O register base address
* @netinfo_task: thread to deliver network status
* @netinfo_waitq: waitq for the thread to sleep
* @deliver_netinfo: to identify whether network status received
* @mac_addrs: INIC mac address
* @link_state: network link state
* @atx_idx: index of async tx channel
*/
struct dim2_hdm {
struct device dev;
struct hdm_channel hch[DMA_CHANNELS];
struct most_channel_capability capabilities[DMA_CHANNELS];
struct most_interface most_iface;
char name[16 + sizeof "dim2-"];
void __iomem *io_base;
u8 clk_speed;
struct clk *clk;
struct clk *clk_pll;
struct task_struct *netinfo_task;
wait_queue_head_t netinfo_waitq;
int deliver_netinfo;
unsigned char mac_addrs[6];
unsigned char link_state;
int atx_idx;
struct medialb_bus bus;
void (*on_netinfo)(struct most_interface *most_iface,
unsigned char link_state, unsigned char *addrs);
void (*disable_platform)(struct platform_device *);
};
struct dim2_platform_data {
int (*enable)(struct platform_device *);
void (*disable)(struct platform_device *);
};
#define iface_to_hdm(iface) container_of(iface, struct dim2_hdm, most_iface)
/* Macro to identify a network status message */
#define PACKET_IS_NET_INFO(p) \
(((p)[1] == 0x18) && ((p)[2] == 0x05) && ((p)[3] == 0x0C) && \
((p)[13] == 0x3C) && ((p)[14] == 0x00) && ((p)[15] == 0x0A))
bool dim2_sysfs_get_state_cb(void)
{
bool state;
unsigned long flags;
spin_lock_irqsave(&dim_lock, flags);
state = dim_get_lock_state();
spin_unlock_irqrestore(&dim_lock, flags);
return state;
}
/**
* dimcb_on_error - callback from HAL to report miscommunication between
* HDM and HAL
* @error_id: Error ID
* @error_message: Error message. Some text in a free format
*/
void dimcb_on_error(u8 error_id, const char *error_message)
{
pr_err("%s: error_id - %d, error_message - %s\n", __func__, error_id,
error_message);
}
/**
* try_start_dim_transfer - try to transfer a buffer on a channel
* @hdm_ch: channel specific data
*
* Transfer a buffer from pending_list if the channel is ready
*/
static int try_start_dim_transfer(struct hdm_channel *hdm_ch)
{
u16 buf_size;
struct list_head *head = &hdm_ch->pending_list;
struct mbo *mbo;
unsigned long flags;
struct dim_ch_state_t st;
BUG_ON(!hdm_ch);
BUG_ON(!hdm_ch->is_initialized);
spin_lock_irqsave(&dim_lock, flags);
if (list_empty(head)) {