cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

hdac_ext_controller.c (9424B)


      1// SPDX-License-Identifier: GPL-2.0-only
      2/*
      3 *  hdac-ext-controller.c - HD-audio extended controller functions.
      4 *
      5 *  Copyright (C) 2014-2015 Intel Corp
      6 *  Author: Jeeja KP <jeeja.kp@intel.com>
      7 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      8 *
      9 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     10 */
     11
     12#include <linux/delay.h>
     13#include <linux/slab.h>
     14#include <sound/hda_register.h>
     15#include <sound/hdaudio_ext.h>
     16
     17/*
     18 * maximum HDAC capablities we should parse to avoid endless looping:
     19 * currently we have 4 extended caps, so this is future proof for now.
     20 * extend when this limit is seen meeting in real HW
     21 */
     22#define HDAC_MAX_CAPS 10
     23
     24/*
     25 * processing pipe helpers - these helpers are useful for dealing with HDA
     26 * new capability of processing pipelines
     27 */
     28
     29/**
     30 * snd_hdac_ext_bus_ppcap_enable - enable/disable processing pipe capability
     31 * @bus: the pointer to HDAC bus object
     32 * @enable: flag to turn on/off the capability
     33 */
     34void snd_hdac_ext_bus_ppcap_enable(struct hdac_bus *bus, bool enable)
     35{
     36
     37	if (!bus->ppcap) {
     38		dev_err(bus->dev, "Address of PP capability is NULL");
     39		return;
     40	}
     41
     42	if (enable)
     43		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
     44				 AZX_PPCTL_GPROCEN, AZX_PPCTL_GPROCEN);
     45	else
     46		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
     47				 AZX_PPCTL_GPROCEN, 0);
     48}
     49EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_enable);
     50
     51/**
     52 * snd_hdac_ext_bus_ppcap_int_enable - ppcap interrupt enable/disable
     53 * @bus: the pointer to HDAC bus object
     54 * @enable: flag to enable/disable interrupt
     55 */
     56void snd_hdac_ext_bus_ppcap_int_enable(struct hdac_bus *bus, bool enable)
     57{
     58
     59	if (!bus->ppcap) {
     60		dev_err(bus->dev, "Address of PP capability is NULL\n");
     61		return;
     62	}
     63
     64	if (enable)
     65		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
     66				 AZX_PPCTL_PIE, AZX_PPCTL_PIE);
     67	else
     68		snd_hdac_updatel(bus->ppcap, AZX_REG_PP_PPCTL,
     69				 AZX_PPCTL_PIE, 0);
     70}
     71EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_ppcap_int_enable);
     72
     73/*
     74 * Multilink helpers - these helpers are useful for dealing with HDA
     75 * new multilink capability
     76 */
     77
     78/**
     79 * snd_hdac_ext_bus_get_ml_capabilities - get multilink capability
     80 * @bus: the pointer to HDAC bus object
     81 *
     82 * This will parse all links and read the mlink capabilities and add them
     83 * in hlink_list of extended hdac bus
     84 * Note: this will be freed on bus exit by driver
     85 */
     86int snd_hdac_ext_bus_get_ml_capabilities(struct hdac_bus *bus)
     87{
     88	int idx;
     89	u32 link_count;
     90	struct hdac_ext_link *hlink;
     91
     92	link_count = readl(bus->mlcap + AZX_REG_ML_MLCD) + 1;
     93
     94	dev_dbg(bus->dev, "In %s Link count: %d\n", __func__, link_count);
     95
     96	for (idx = 0; idx < link_count; idx++) {
     97		hlink  = kzalloc(sizeof(*hlink), GFP_KERNEL);
     98		if (!hlink)
     99			return -ENOMEM;
    100		hlink->index = idx;
    101		hlink->bus = bus;
    102		hlink->ml_addr = bus->mlcap + AZX_ML_BASE +
    103					(AZX_ML_INTERVAL * idx);
    104		hlink->lcaps  = readl(hlink->ml_addr + AZX_REG_ML_LCAP);
    105		hlink->lsdiid = readw(hlink->ml_addr + AZX_REG_ML_LSDIID);
    106
    107		/* since link in On, update the ref */
    108		hlink->ref_count = 1;
    109
    110		list_add_tail(&hlink->list, &bus->hlink_list);
    111	}
    112
    113	return 0;
    114}
    115EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_ml_capabilities);
    116
    117/**
    118 * snd_hdac_link_free_all- free hdac extended link objects
    119 *
    120 * @bus: the pointer to HDAC bus object
    121 */
    122
    123void snd_hdac_link_free_all(struct hdac_bus *bus)
    124{
    125	struct hdac_ext_link *l;
    126
    127	while (!list_empty(&bus->hlink_list)) {
    128		l = list_first_entry(&bus->hlink_list, struct hdac_ext_link, list);
    129		list_del(&l->list);
    130		kfree(l);
    131	}
    132}
    133EXPORT_SYMBOL_GPL(snd_hdac_link_free_all);
    134
    135/**
    136 * snd_hdac_ext_bus_link_at - get link at specified address
    137 * @bus: link's parent bus device
    138 * @addr: codec device address
    139 *
    140 * Returns link object or NULL if matching link is not found.
    141 */
    142struct hdac_ext_link *snd_hdac_ext_bus_link_at(struct hdac_bus *bus, int addr)
    143{
    144	struct hdac_ext_link *hlink;
    145	int i;
    146
    147	list_for_each_entry(hlink, &bus->hlink_list, list)
    148		for (i = 0; i < HDA_MAX_CODECS; i++)
    149			if (hlink->lsdiid & (0x1 << addr))
    150				return hlink;
    151	return NULL;
    152}
    153EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_at);
    154
    155/**
    156 * snd_hdac_ext_bus_get_link - get link based on codec name
    157 * @bus: the pointer to HDAC bus object
    158 * @codec_name: codec name
    159 */
    160struct hdac_ext_link *snd_hdac_ext_bus_get_link(struct hdac_bus *bus,
    161						 const char *codec_name)
    162{
    163	int bus_idx, addr;
    164
    165	if (sscanf(codec_name, "ehdaudio%dD%d", &bus_idx, &addr) != 2)
    166		return NULL;
    167	if (bus->idx != bus_idx)
    168		return NULL;
    169	if (addr < 0 || addr > 31)
    170		return NULL;
    171
    172	return snd_hdac_ext_bus_link_at(bus, addr);
    173}
    174EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_get_link);
    175
    176static int check_hdac_link_power_active(struct hdac_ext_link *link, bool enable)
    177{
    178	int timeout;
    179	u32 val;
    180	int mask = (1 << AZX_MLCTL_CPA_SHIFT);
    181
    182	udelay(3);
    183	timeout = 150;
    184
    185	do {
    186		val = readl(link->ml_addr + AZX_REG_ML_LCTL);
    187		if (enable) {
    188			if (((val & mask) >> AZX_MLCTL_CPA_SHIFT))
    189				return 0;
    190		} else {
    191			if (!((val & mask) >> AZX_MLCTL_CPA_SHIFT))
    192				return 0;
    193		}
    194		udelay(3);
    195	} while (--timeout);
    196
    197	return -EIO;
    198}
    199
    200/**
    201 * snd_hdac_ext_bus_link_power_up -power up hda link
    202 * @link: HD-audio extended link
    203 */
    204int snd_hdac_ext_bus_link_power_up(struct hdac_ext_link *link)
    205{
    206	snd_hdac_updatel(link->ml_addr, AZX_REG_ML_LCTL,
    207			 AZX_MLCTL_SPA, AZX_MLCTL_SPA);
    208
    209	return check_hdac_link_power_active(link, true);
    210}
    211EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up);
    212
    213/**
    214 * snd_hdac_ext_bus_link_power_down -power down hda link
    215 * @link: HD-audio extended link
    216 */
    217int snd_hdac_ext_bus_link_power_down(struct hdac_ext_link *link)
    218{
    219	snd_hdac_updatel(link->ml_addr, AZX_REG_ML_LCTL, AZX_MLCTL_SPA, 0);
    220
    221	return check_hdac_link_power_active(link, false);
    222}
    223EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down);
    224
    225/**
    226 * snd_hdac_ext_bus_link_power_up_all -power up all hda link
    227 * @bus: the pointer to HDAC bus object
    228 */
    229int snd_hdac_ext_bus_link_power_up_all(struct hdac_bus *bus)
    230{
    231	struct hdac_ext_link *hlink = NULL;
    232	int ret;
    233
    234	list_for_each_entry(hlink, &bus->hlink_list, list) {
    235		snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
    236				 AZX_MLCTL_SPA, AZX_MLCTL_SPA);
    237		ret = check_hdac_link_power_active(hlink, true);
    238		if (ret < 0)
    239			return ret;
    240	}
    241
    242	return 0;
    243}
    244EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_up_all);
    245
    246/**
    247 * snd_hdac_ext_bus_link_power_down_all -power down all hda link
    248 * @bus: the pointer to HDAC bus object
    249 */
    250int snd_hdac_ext_bus_link_power_down_all(struct hdac_bus *bus)
    251{
    252	struct hdac_ext_link *hlink = NULL;
    253	int ret;
    254
    255	list_for_each_entry(hlink, &bus->hlink_list, list) {
    256		snd_hdac_updatel(hlink->ml_addr, AZX_REG_ML_LCTL,
    257				 AZX_MLCTL_SPA, 0);
    258		ret = check_hdac_link_power_active(hlink, false);
    259		if (ret < 0)
    260			return ret;
    261	}
    262
    263	return 0;
    264}
    265EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power_down_all);
    266
    267int snd_hdac_ext_bus_link_get(struct hdac_bus *bus,
    268				struct hdac_ext_link *link)
    269{
    270	unsigned long codec_mask;
    271	int ret = 0;
    272
    273	mutex_lock(&bus->lock);
    274
    275	/*
    276	 * if we move from 0 to 1, count will be 1 so power up this link
    277	 * as well, also check the dma status and trigger that
    278	 */
    279	if (++link->ref_count == 1) {
    280		if (!bus->cmd_dma_state) {
    281			snd_hdac_bus_init_cmd_io(bus);
    282			bus->cmd_dma_state = true;
    283		}
    284
    285		ret = snd_hdac_ext_bus_link_power_up(link);
    286
    287		/*
    288		 * clear the register to invalidate all the output streams
    289		 */
    290		snd_hdac_updatew(link->ml_addr, AZX_REG_ML_LOSIDV,
    291				 ML_LOSIDV_STREAM_MASK, 0);
    292		/*
    293		 *  wait for 521usec for codec to report status
    294		 *  HDA spec section 4.3 - Codec Discovery
    295		 */
    296		udelay(521);
    297		codec_mask = snd_hdac_chip_readw(bus, STATESTS);
    298		dev_dbg(bus->dev, "codec_mask = 0x%lx\n", codec_mask);
    299		snd_hdac_chip_writew(bus, STATESTS, codec_mask);
    300		if (!bus->codec_mask)
    301			bus->codec_mask = codec_mask;
    302	}
    303
    304	mutex_unlock(&bus->lock);
    305	return ret;
    306}
    307EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_get);
    308
    309int snd_hdac_ext_bus_link_put(struct hdac_bus *bus,
    310				struct hdac_ext_link *link)
    311{
    312	int ret = 0;
    313	struct hdac_ext_link *hlink;
    314	bool link_up = false;
    315
    316	mutex_lock(&bus->lock);
    317
    318	/*
    319	 * if we move from 1 to 0, count will be 0
    320	 * so power down this link as well
    321	 */
    322	if (--link->ref_count == 0) {
    323		ret = snd_hdac_ext_bus_link_power_down(link);
    324
    325		/*
    326		 * now check if all links are off, if so turn off
    327		 * cmd dma as well
    328		 */
    329		list_for_each_entry(hlink, &bus->hlink_list, list) {
    330			if (hlink->ref_count) {
    331				link_up = true;
    332				break;
    333			}
    334		}
    335
    336		if (!link_up) {
    337			snd_hdac_bus_stop_cmd_io(bus);
    338			bus->cmd_dma_state = false;
    339		}
    340	}
    341
    342	mutex_unlock(&bus->lock);
    343	return ret;
    344}
    345EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_put);
    346
    347static void hdac_ext_codec_link_up(struct hdac_device *codec)
    348{
    349	const char *devname = dev_name(&codec->dev);
    350	struct hdac_ext_link *hlink =
    351		snd_hdac_ext_bus_get_link(codec->bus, devname);
    352
    353	if (hlink)
    354		snd_hdac_ext_bus_link_get(codec->bus, hlink);
    355}
    356
    357static void hdac_ext_codec_link_down(struct hdac_device *codec)
    358{
    359	const char *devname = dev_name(&codec->dev);
    360	struct hdac_ext_link *hlink =
    361		snd_hdac_ext_bus_get_link(codec->bus, devname);
    362
    363	if (hlink)
    364		snd_hdac_ext_bus_link_put(codec->bus, hlink);
    365}
    366
    367void snd_hdac_ext_bus_link_power(struct hdac_device *codec, bool enable)
    368{
    369	struct hdac_bus *bus = codec->bus;
    370	bool oldstate = test_bit(codec->addr, &bus->codec_powered);
    371
    372	if (enable == oldstate)
    373		return;
    374
    375	snd_hdac_bus_link_power(codec, enable);
    376
    377	if (enable)
    378		hdac_ext_codec_link_up(codec);
    379	else
    380		hdac_ext_codec_link_down(codec);
    381}
    382EXPORT_SYMBOL_GPL(snd_hdac_ext_bus_link_power);