summaryrefslogtreecommitdiff
path: root/sound/pci/hda/hda_i915.c
blob: 66acd09f5da179cbfb8c4a4f687140c0cc22024f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
 *  hda_i915.c - routines for Haswell HDA controller power well support
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 *  for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include <linux/init.h>
#include <linux/module.h>
#include <sound/core.h>
#include <drm/i915_powerwell.h>
#include "hda_priv.h"
#include "hda_intel.h"

/* Intel HSW/BDW display HDA controller Extended Mode registers.
 * EM4 (M value) and EM5 (N Value) are used to convert CDClk (Core Display
 * Clock) to 24MHz BCLK: BCLK = CDCLK * M / N
 * The values will be lost when the display power well is disabled.
 */
#define AZX_REG_EM4			0x100c
#define AZX_REG_EM5			0x1010

static int (*get_power)(void);
static int (*put_power)(void);
static int (*get_cdclk)(void);

int hda_display_power(struct hda_intel *hda, bool enable)
{
	if (!get_power || !put_power)
		return -ENODEV;

	pr_debug("HDA display power %s \n",
			enable ? "Enable" : "Disable");
	if (enable)
		return get_power();
	else
		return put_power();
}

void haswell_set_bclk(struct hda_intel *hda)
{
	int cdclk_freq;
	unsigned int bclk_m, bclk_n;

	if (!get_cdclk)
		return;

	cdclk_freq = get_cdclk();
	switch (cdclk_freq) {
	case 337500:
		bclk_m = 16;
		bclk_n = 225;
		break;

	case 450000:
	default: /* default CDCLK 450MHz */
		bclk_m = 4;
		bclk_n = 75;
		break;

	case 540000:
		bclk_m = 4;
		bclk_n = 90;
		break;

	case 675000:
		bclk_m = 8;
		bclk_n = 225;
		break;
	}

	azx_writew(&hda->chip, EM4, bclk_m);
	azx_writew(&hda->chip, EM5, bclk_n);
}


int hda_i915_init(struct hda_intel *hda)
{
	int err = 0;

	get_power = symbol_request(i915_request_power_well);
	if (!get_power) {
		pr_warn("hda-i915: get_power symbol get fail\n");
		return -ENODEV;
	}

	put_power = symbol_request(i915_release_power_well);
	if (!put_power) {
		symbol_put(i915_request_power_well);
		get_power = NULL;
		return -ENODEV;
	}

	get_cdclk = symbol_request(i915_get_cdclk_freq);
	if (!get_cdclk)	/* may have abnormal BCLK and audio playback rate */
		pr_warn("hda-i915: get_cdclk symbol get fail\n");

	pr_debug("HDA driver get symbol successfully from i915 module\n");

	return err;
}

int hda_i915_exit(struct hda_intel *hda)
{
	if (get_power) {
		symbol_put(i915_request_power_well);
		get_power = NULL;
	}
	if (put_power) {
		symbol_put(i915_release_power_well);
		put_power = NULL;
	}
	if (get_cdclk) {
		symbol_put(i915_get_cdclk_freq);
		get_cdclk = NULL;
	}

	return 0;
}