// 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/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 <linux/of.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);
/**
* struct hdm_channel - private structure to keep channel specific data
* @name: channel name
* @is_initialized: identifier to know whether the channel is initialized
* @ch: HAL specific channel data
* @reset_dbr_size: reset DBR data buffer size
* @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 *pdev);
};
struct dim2_platform_data {
int (*enable)(struct platform_device *pdev);
void (*disable)(struct platform_device *pdev);
u8 fcnt;
};
static inline struct dim2_hdm *iface_to_hdm(struct most_interface *iface)
{
return 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))
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
bool state;
unsigned long flags;
spin_lock_irqsave(&dim_lock, flags);
state = dim_get_lock_state();
spin_unlock_irqrestore(&dim_lock, flags);
return sysfs_emit(buf, "%s\n", state ? "locked" : "");
}
static DEVICE_ATTR_RO(state);
static struct attribute *dim2_attrs[] = {
&dev_attr_state.attr,
NULL,
};
ATTRIBUTE_GROUPS(dim2);
/**
* 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 st;
BUG_ON(!hdm_ch);
BUG_ON(!hdm_ch->is_initialized);
spin_lock_irqsave(&dim_lock, flags);
if (list_empty(head)) {
spin_unlock_irqrestore(&dim_lock, flags);
return -EAGAIN;
}
if (!dim_get_channel_state(&hdm_ch->ch, &st)->ready) {
spin_unlock_irqrestore(&dim_lock, flags);
return -EAGAIN;
}
mbo = list_first_entry(head, struct mbo, list);
buf_size = mbo->buffer_length;
if (dim_dbr_space(&hdm_ch->ch) < buf_size)