/*
* Internal Thunderbolt Connection Manager. This is a firmware running on
* the Thunderbolt host controller performing most of the low-level
* handling.
*
* Copyright (C) 2017, Intel Corporation
* Authors: Michael Jamet <michael.jamet@intel.com>
* Mika Westerberg <mika.westerberg@linux.intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_data/x86/apple.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "ctl.h"
#include "nhi_regs.h"
#include "tb.h"
#define PCIE2CIO_CMD 0x30
#define PCIE2CIO_CMD_TIMEOUT BIT(31)
#define PCIE2CIO_CMD_START BIT(30)
#define PCIE2CIO_CMD_WRITE BIT(21)
#define PCIE2CIO_CMD_CS_MASK GENMASK(20, 19)
#define PCIE2CIO_CMD_CS_SHIFT 19
#define PCIE2CIO_CMD_PORT_MASK GENMASK(18, 13)
#define PCIE2CIO_CMD_PORT_SHIFT 13
#define PCIE2CIO_WRDATA 0x34
#define PCIE2CIO_RDDATA 0x38
#define PHY_PORT_CS1 0x37
#define PHY_PORT_CS1_LINK_DISABLE BIT(14)
#define PHY_PORT_CS1_LINK_STATE_MASK GENMASK(29, 26)
#define PHY_PORT_CS1_LINK_STATE_SHIFT 26
#define ICM_TIMEOUT 5000 /* ms */
#define ICM_MAX_LINK 4
#define ICM_MAX_DEPTH 6
/**
* struct icm - Internal connection manager private data
* @request_lock: Makes sure only one message is send to ICM at time
* @rescan_work: Work used to rescan the surviving switches after resume
* @upstream_port: Pointer to the PCIe upstream port this host
* controller is connected. This is only set for systems
* where ICM needs to be started manually
* @vnd_cap: Vendor defined capability where PCIe2CIO mailbox resides
* (only set when @upstream_port is not %NULL)
* @safe_mode: ICM is in safe mode
* @is_supported: Checks if we can support ICM on this controller
* @get_mode: Read and return the ICM firmware mode (optional)
* @get_route: Find a route string for given switch
* @device_connected: Handle device connected ICM message
* @device_disconnected: Handle device disconnected ICM message
*/
struct icm {
struct mutex request_lock;
struct delayed_work rescan_work;
struct pci_dev *upstream_port;
int vnd_cap;
bool safe_mode;
bool (*is_supported)(struct tb *tb);
int (*get_mode)(struct tb *tb);
int (*get_route)(struct tb *tb, u8 link, u8 depth, u64 *route);
void (*device_connected)(struct tb *tb,
const struct icm_pkg_header *hdr);
void (*device_disconnected)(struct tb *tb,
const struct icm_pkg_header *hdr);
};
struct icm_notification {
struct work_struct work;
struct icm_pkg_header *pkg;
struct tb *tb;
};
static inline struct tb *icm_to_tb(struct icm *icm)
{
return ((void *)icm - sizeof(struct tb));
}
static inline u8 phy_port_from_route(u64 route, u8 depth)
{
return tb_switch_phy_port_from_link(route >> ((depth - 1) * 8));
}
static inline u8 dual_link_from_link(u8 link)
{
return link ? ((link - 1) ^ 0x01) + 1 : 0;
}
static inline u64 get_route(u32 route_hi, u32 route_lo)
{
return (u64)route_hi << 32 | route_lo;
}
static bool icm_match(const struct tb_cfg_request *req,
const struct ctl_pkg *pkg)
{
const struct icm_pkg_header *res_hdr = pkg->buffer;
const struct icm_pkg_header *req_hdr = req->request;
if (pkg->frame.eof != req->response_type)
return false;
if (res_hdr->code != req_hdr->code)
return false;
return true;
}
static bool icm_copy(struct tb_cfg_request *req, const struct ctl_pkg *pkg)
{
const struct icm_pkg_header *hdr = pkg->buffer;
if (hdr->packet_id < req->npackets) {
size_t offset = hdr->packet_id * req->response_size;
memcpy(req->response + offset, pkg->buffer, req->response_size);
}
return hdr->packet_id == hdr->total_packets - 1;
}
static int icm_request(struct tb *tb, const void *request, size_t request_size,
void *response, size_t response_size, size_t npackets,
unsigned int timeout_msec)
{
struct icm *icm = tb_priv(tb);
int retries = 3;
do {
struct tb_cfg_request *req;
struct tb_cfg_result res;
req = tb_cfg_request_alloc();
if (!req)
return -ENOMEM;
req->match = icm_match;
req->copy = icm_copy;
req->request = request;
req->request_size = request_size;
req->request_type = TB_CFG_PKG_ICM_CMD;
req->response = response;
req->npackets = npackets;
req->response_size = response_size;
req->response_type = TB_CFG_PKG_ICM_RESP;
mutex_lock(&icm->request_lock);
res = tb_cfg_request_sync(tb->ctl, req, timeout_msec);
mutex_unlock(&icm->request_lock);
tb_cfg_request_put(req);
if (res.err != -ETIMEDOUT)
return res.err == 1 ? -EIO : res.err;
usleep_range