Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 49d31bb2 authored by Ajay Singh Parmar's avatar Ajay Singh Parmar
Browse files

msm: mdss: hdmi: separate audio from transmitter core



Create an audio module and separate the audio related
functionalities from hdmi driver for a cleaner approach.

Change-Id: I15016d032767ee1c14e026915558a63490c6fbc9
Signed-off-by: default avatarAjay Singh Parmar <aparmar@codeaurora.org>
parent 753b4916
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_tx.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_hdcp.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_hdcp2p2.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_cec.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_PANEL) += mdss_hdmi_audio.o
obj-$(CONFIG_FB_MSM_MDSS_HDMI_MHL_SII8334) += mhl_sii8334.o mhl_msc.o

obj-$(CONFIG_FB_MSM_MDSS_WRITEBACK) += mdss_wb.o
+525 −0
Original line number Diff line number Diff line
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 */

#define pr_fmt(fmt)	"%s: " fmt, __func__

#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/iopoll.h>
#include <linux/types.h>
#include <linux/switch.h>
#include <linux/gcd.h>

#include "mdss_hdmi_audio.h"
#include "mdss_hdmi_util.h"

#define HDMI_AUDIO_INFO_FRAME_PACKET_HEADER 0x84
#define HDMI_AUDIO_INFO_FRAME_PACKET_VERSION 0x1
#define HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH 0x0A

#define HDMI_KHZ_TO_HZ 1000
#define HDMI_MHZ_TO_HZ 1000000
#define HDMI_ACR_N_MULTIPLIER 128
#define DEFAULT_AUDIO_SAMPLE_RATE_HZ 48000

/* Supported HDMI Audio channels */
enum hdmi_audio_channels {
	AUDIO_CHANNEL_2 = 2,
	AUDIO_CHANNEL_3,
	AUDIO_CHANNEL_4,
	AUDIO_CHANNEL_5,
	AUDIO_CHANNEL_6,
	AUDIO_CHANNEL_7,
	AUDIO_CHANNEL_8,
};

/* parameters for clock regeneration */
struct hdmi_audio_acr {
	u32 n;
	u32 cts;
};

enum hdmi_audio_sample_rates {
	AUDIO_SAMPLE_RATE_32KHZ,
	AUDIO_SAMPLE_RATE_44_1KHZ,
	AUDIO_SAMPLE_RATE_48KHZ,
	AUDIO_SAMPLE_RATE_88_2KHZ,
	AUDIO_SAMPLE_RATE_96KHZ,
	AUDIO_SAMPLE_RATE_176_4KHZ,
	AUDIO_SAMPLE_RATE_192KHZ,
	AUDIO_SAMPLE_RATE_MAX
};

struct hdmi_audio {
	struct dss_io_data *io;
	struct msm_hdmi_audio_setup_params params;
	struct switch_dev sdev;
	u32 pclk;
	bool ack_enabled;
	bool audio_ack_enabled;
	atomic_t ack_pending;
};

static void hdmi_audio_get_audio_sample_rate(u32 *sample_rate_hz)
{
	u32 rate = *sample_rate_hz;

	switch (rate) {
	case 32000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_32KHZ;
		break;
	case 44100:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_44_1KHZ;
		break;
	case 48000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_48KHZ;
		break;
	case 88200:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_88_2KHZ;
		break;
	case 96000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_96KHZ;
		break;
	case 176400:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_176_4KHZ;
		break;
	case 192000:
		*sample_rate_hz = AUDIO_SAMPLE_RATE_192KHZ;
		break;
	default:
		pr_debug("%d unchanged\n", rate);
		break;
	}
}

static void hdmi_audio_get_acr_param(u32 pclk, u32 fs,
	struct hdmi_audio_acr *acr)
{
	u32 div, mul;

	if (!acr) {
		pr_err("invalid data\n");
		return;
	}

	/*
	 * as per HDMI specification, N/CTS = (128*fs)/pclk.
	 * get the ratio using this formula.
	 */
	acr->n = HDMI_ACR_N_MULTIPLIER * fs;
	acr->cts = pclk;

	/* get the greatest common divisor for the ratio */
	div = gcd(acr->n, acr->cts);

	/* get the n and cts values wrt N/CTS formula */
	acr->n /= div;
	acr->cts /= div;

	/*
	 * as per HDMI specification, 300 <= 128*fs/N <= 1500
	 * with a target of 128*fs/N = 1000. To get closest
	 * value without truncating fractional values, find
	 * the corresponding multiplier
	 */
	mul = ((HDMI_ACR_N_MULTIPLIER * fs / HDMI_KHZ_TO_HZ)
		+ (acr->n - 1)) / acr->n;

	acr->n *= mul;
	acr->cts *= mul;
}

static void hdmi_audio_acr_enable(struct hdmi_audio *audio)
{
	struct dss_io_data *io;
	struct hdmi_audio_acr acr;
	struct msm_hdmi_audio_setup_params *params;
	u32 pclk, layout, multiplier, sample_rate;
	u32 acr_pkt_ctl, aud_pkt_ctl2, acr_reg_cts, acr_reg_n;

	if (!audio) {
		pr_err("invalid input\n");
		return;
	}

	io = audio->io;
	params = &audio->params;
	pclk = audio->pclk;
	sample_rate = params->sample_rate_hz;

	hdmi_audio_get_acr_param(pclk * HDMI_KHZ_TO_HZ, sample_rate, &acr);
	hdmi_audio_get_audio_sample_rate(&sample_rate);

	layout = AUDIO_CHANNEL_2 == params->num_of_channels ? 0 : 1;

	pr_debug("n=%u, cts=%u, layout=%u\n", acr.n, acr.cts, layout);

	/* AUDIO_PRIORITY | SOURCE */
	acr_pkt_ctl = BIT(31) | BIT(8);

	/* N_MULTIPLE(multiplier) */
	acr_pkt_ctl |= (multiplier & 0x7) << 16;

	switch (sample_rate) {
	case AUDIO_SAMPLE_RATE_44_1KHZ:
		acr_pkt_ctl |= 0x2 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_44_0;
		acr_reg_n = HDMI_ACR_44_1;
		break;
	case AUDIO_SAMPLE_RATE_48KHZ:
		acr_pkt_ctl |= 0x3 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_48_0;
		acr_reg_n = HDMI_ACR_48_1;
		break;
	case AUDIO_SAMPLE_RATE_192KHZ:
		multiplier = 4;
		acr.n >>= 2;

		acr_pkt_ctl |= 0x3 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_48_0;
		acr_reg_n = HDMI_ACR_48_1;
		break;
	case AUDIO_SAMPLE_RATE_176_4KHZ:
		multiplier = 4;
		acr.n >>= 2;

		acr_pkt_ctl |= 0x2 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_44_0;
		acr_reg_n = HDMI_ACR_44_1;
		break;
	case AUDIO_SAMPLE_RATE_96KHZ:
		multiplier = 2;
		acr.n >>= 1;

		acr_pkt_ctl |= 0x3 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_48_0;
		acr_reg_n = HDMI_ACR_48_1;
		break;
	case AUDIO_SAMPLE_RATE_88_2KHZ:
		multiplier = 2;
		acr.n >>= 1;

		acr_pkt_ctl |= 0x2 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_44_0;
		acr_reg_n = HDMI_ACR_44_1;
		break;
	default:
		multiplier = 1;

		acr_pkt_ctl |= 0x1 << 4;
		acr.cts <<= 12;

		acr_reg_cts = HDMI_ACR_32_0;
		acr_reg_n = HDMI_ACR_32_1;
		break;
	}

	aud_pkt_ctl2 = BIT(0) | (layout << 1);

	/* SEND | CONT */
	acr_pkt_ctl |= BIT(0) | BIT(1);

	DSS_REG_W(io, acr_reg_cts, acr.cts);
	DSS_REG_W(io, acr_reg_n, acr.n);
	DSS_REG_W(io, HDMI_ACR_PKT_CTRL, acr_pkt_ctl);
	DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL2, aud_pkt_ctl2);
}

static void hdmi_audio_acr_setup(struct hdmi_audio *audio, bool on)
{
	if (on)
		hdmi_audio_acr_enable(audio);
	else
		DSS_REG_W(audio->io, HDMI_ACR_PKT_CTRL, 0);
}

static void hdmi_audio_infoframe_setup(struct hdmi_audio *audio, bool enabled)
{
	struct dss_io_data *io = NULL;
	u32 channels, channel_allocation, level_shift, down_mix, layout;
	u32 hdmi_debug_reg = 0, audio_info_0_reg = 0, audio_info_1_reg = 0;
	u32 audio_info_ctrl_reg, aud_pck_ctrl_2_reg;
	u32 check_sum, sample_present;

	if (!audio) {
		pr_err("invalid input\n");
		return;
	}

	io = audio->io;
	if (!io->base) {
		pr_err("core io not inititalized\n");
		return;
	}

	audio_info_ctrl_reg = DSS_REG_R(io, HDMI_INFOFRAME_CTRL0);
	audio_info_ctrl_reg &= ~0xF0;

	if (!enabled)
		goto end;

	channels           = audio->params.num_of_channels - 1;
	channel_allocation = audio->params.channel_allocation;
	level_shift        = audio->params.level_shift;
	down_mix           = audio->params.down_mix;
	sample_present     = audio->params.sample_present;

	layout = AUDIO_CHANNEL_2 == audio->params.num_of_channels ? 0 : 1;
	aud_pck_ctrl_2_reg = BIT(0) | (layout << 1);
	DSS_REG_W(io, HDMI_AUDIO_PKT_CTRL2, aud_pck_ctrl_2_reg);

	audio_info_1_reg |= channel_allocation & 0xFF;
	audio_info_1_reg |= ((level_shift & 0xF) << 11);
	audio_info_1_reg |= ((down_mix & 0x1) << 15);

	check_sum = 0;
	check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_HEADER;
	check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_VERSION;
	check_sum += HDMI_AUDIO_INFO_FRAME_PACKET_LENGTH;
	check_sum += channels;
	check_sum += channel_allocation;
	check_sum += (level_shift & 0xF) << 3 | (down_mix & 0x1) << 7;
	check_sum &= 0xFF;
	check_sum = (u8) (256 - check_sum);

	audio_info_0_reg |= check_sum & 0xFF;
	audio_info_0_reg |= ((channels & 0x7) << 8);

	/* Enable Audio InfoFrame Transmission */
	audio_info_ctrl_reg |= 0xF0;

	if (layout) {
		/* Set the Layout bit */
		hdmi_debug_reg |= BIT(4);

		/* Set the Sample Present bits */
		hdmi_debug_reg |= sample_present & 0xF;
	}
end:
	DSS_REG_W(io, HDMI_DEBUG, hdmi_debug_reg);
	DSS_REG_W(io, HDMI_AUDIO_INFO0, audio_info_0_reg);
	DSS_REG_W(io, HDMI_AUDIO_INFO1, audio_info_1_reg);
	DSS_REG_W(io, HDMI_INFOFRAME_CTRL0, audio_info_ctrl_reg);
}

static int hdmi_audio_on(void *ctx, u32 pclk,
	struct msm_hdmi_audio_setup_params *params)
{
	struct hdmi_audio *audio = ctx;
	int rc = 0;

	if (!audio) {
		pr_err("invalid input\n");
		rc = -EINVAL;
		goto end;
	}

	audio->pclk = pclk;
	audio->params = *params;

	if (!audio->params.num_of_channels) {
		audio->params.sample_rate_hz = DEFAULT_AUDIO_SAMPLE_RATE_HZ;
		audio->params.num_of_channels = AUDIO_CHANNEL_2;
	}

	hdmi_audio_acr_setup(audio, true);
	hdmi_audio_infoframe_setup(audio, true);

	pr_debug("HDMI Audio: Enabled\n");
end:
	return rc;
}

static void hdmi_audio_off(void *ctx)
{
	struct hdmi_audio *audio = ctx;

	if (!audio) {
		pr_err("invalid input\n");
		return;
	}

	hdmi_audio_infoframe_setup(audio, false);
	hdmi_audio_acr_setup(audio, false);

	pr_debug("HDMI Audio: Disabled\n");
}

static void hdmi_audio_notify(void *ctx, int val)
{
	struct hdmi_audio *audio = ctx;
	int state = 0;
	bool switched;

	if (!audio) {
		pr_err("invalid input\n");
		return;
	}

	state = audio->sdev.state;
	if (state == val)
		return;

	if (audio->ack_enabled &&
		atomic_read(&audio->ack_pending)) {
		pr_err("%s ack pending, not notifying %s\n",
			state ? "connect" : "disconnect",
			val ? "connect" : "disconnect");
		return;
	}

	switch_set_state(&audio->sdev, val);
	switched = audio->sdev.state != state;

	if (audio->ack_enabled && switched)
		atomic_set(&audio->ack_pending, 1);

	pr_debug("audio %s %s\n", switched ? "switched to" : "same as",
		audio->sdev.state ? "HDMI" : "SPKR");
}

static void hdmi_audio_ack(void *ctx, u32 ack, u32 hpd)
{
	struct hdmi_audio *audio = ctx;
	u32 ack_hpd;

	if (!audio) {
		pr_err("invalid input\n");
		return;
	}

	if (ack & AUDIO_ACK_SET_ENABLE) {
		audio->ack_enabled = ack & AUDIO_ACK_ENABLE ?
			true : false;

		pr_debug("audio ack feature %s\n",
			audio->ack_enabled ? "enabled" : "disabled");
		return;
	}

	if (!audio->ack_enabled)
		return;

	atomic_set(&audio->ack_pending, 0);

	ack_hpd = ack & AUDIO_ACK_CONNECT;

	pr_debug("acknowledging %s\n",
		ack_hpd ? "connect" : "disconnect");

	if (ack_hpd != hpd) {
		pr_debug("unbalanced audio state, ack %d, hpd %d\n",
			ack_hpd, hpd);

		hdmi_audio_notify(ctx, hpd);
	}
}

static void hdmi_audio_reset(void *ctx)
{
	struct hdmi_audio *audio = ctx;

	if (!audio) {
		pr_err("invalid input\n");
		return;
	}

	atomic_set(&audio->ack_pending, 0);
}

static void hdmi_audio_status(void *ctx, struct hdmi_audio_status *status)
{
	struct hdmi_audio *audio = ctx;

	if (!audio || !status) {
		pr_err("invalid input\n");
		return;
	}

	status->ack_enabled = audio->ack_enabled;
	status->ack_pending = atomic_read(&audio->ack_pending);
	status->switched = audio->sdev.state;
}

/**
 * hdmi_audio_register() - audio registeration function
 * @data: registeration initialization data
 *
 * This API configures audio module for client to use HDMI audio.
 * Provides audio functionalities which client can call.
 * Initializes internal data structures.
 *
 * Return: pointer to audio data that client needs to pass on
 * calling audio functions.
 */
void *hdmi_audio_register(struct hdmi_audio_init_data *data)
{
	struct hdmi_audio *audio = NULL;
	int rc = 0;

	if (!data)
		goto end;

	audio = kzalloc(sizeof(*audio), GFP_KERNEL);
	if (!audio)
		goto end;

	audio->sdev.name = "hdmi_audio";
	rc = switch_dev_register(&audio->sdev);
	if (rc) {
		pr_err("audio switch registration failed\n");
		kzfree(audio);
		goto end;
	}

	audio->io = data->io;

	data->ops->on     = hdmi_audio_on;
	data->ops->off    = hdmi_audio_off;
	data->ops->notify = hdmi_audio_notify;
	data->ops->ack    = hdmi_audio_ack;
	data->ops->reset  = hdmi_audio_reset;
	data->ops->status = hdmi_audio_status;
end:
	return audio;
}

/**
 * hdmi_audio_unregister() - unregister audio module
 * @ctx: audio module's data
 *
 * Delete audio module's instance and allocated resources
 */
void hdmi_audio_unregister(void *ctx)
{
	struct hdmi_audio *audio = ctx;

	if (audio) {
		switch_dev_unregister(&audio->sdev);
		kfree(ctx);
	}
}
+72 −0
Original line number Diff line number Diff line
/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * 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.
 */

#ifndef __MDSS_HDMI_AUDIO_H__
#define __MDSS_HDMI_AUDIO_H__

#include <linux/mdss_io_util.h>
#include <linux/msm_hdmi.h>

#define AUDIO_ACK_SET_ENABLE BIT(5)
#define AUDIO_ACK_ENABLE BIT(4)
#define AUDIO_ACK_CONNECT BIT(0)

/**
 * struct hdmi_audio_status - hdmi audio current status info
 * @ack_pending: notification acknowledgment status
 * @ack_enabled: acknowledgment feature is enabled or disabled
 * @switched: audio notification status for routing
 *
 * Data for client to query about the current status of audio
 */
struct hdmi_audio_status {
	bool ack_pending;
	bool ack_enabled;
	bool switched;
};

/**
 * struct hdmi_audio_ops - audio operations for clients to call
 * @on: function pointer to enable audio
 * @reset: function pointer to reset the audio current status to default
 * @status: function pointer to get the current status of audio
 * @notify: function pointer to notify other modules for audio routing
 * @ack: function pointer to acknowledge audio routing change
 *
 * Provides client operations for audio functionalities
 */
struct hdmi_audio_ops {
	int (*on)(void *ctx, u32 pclk,
		struct msm_hdmi_audio_setup_params *params);
	void (*off)(void *ctx);
	void (*reset)(void *ctx);
	void (*status)(void *ctx, struct hdmi_audio_status *status);
	void (*notify)(void *ctx, int val);
	void (*ack)(void *ctx, u32 ack, u32 hpd);
};

/**
 * struct hdmi_audio_init_data - data needed for initializing audio module
 * @io: pointer to register access related data
 * @ops: pointer to populate operation functions.
 *
 * Defines the data needed to be provided while initializing audio module
 */
struct hdmi_audio_init_data {
	struct dss_io_data *io;
	struct hdmi_audio_ops *ops;
};

void *hdmi_audio_register(struct hdmi_audio_init_data *data);
void hdmi_audio_unregister(void *data);

#endif /* __MDSS_HDMI_AUDIO_H__ */
+56 −560

File changed.

Preview size limit exceeded, changes collapsed.

+4 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
#include <linux/switch.h>
#include "mdss_hdmi_util.h"
#include "mdss_cec_core.h"
#include "mdss_hdmi_audio.h"

#define MAX_SWITCH_NAME_SIZE        5

@@ -130,14 +131,12 @@ struct hdmi_tx_ctrl {


	struct hdmi_tx_pinctrl pin_res;
	struct msm_hdmi_audio_setup_params audio_data;

	struct mutex mutex;
	struct mutex tx_lock;
	struct list_head cable_notify_handlers;
	struct kobject *kobj;
	struct switch_dev sdev;
	struct switch_dev audio_sdev;
	struct workqueue_struct *workq;
	spinlock_t hpd_state_lock;

@@ -168,8 +167,6 @@ struct hdmi_tx_ctrl {
	bool scrambler_enabled;
	u32 hdcp14_present;
	bool hdcp1_use_sw_keys;
	bool audio_ack_enabled;
	atomic_t audio_ack_pending;
	bool hdcp14_sw_keys;
	bool auth_state;
	bool custom_edid;
@@ -183,6 +180,7 @@ struct hdmi_tx_ctrl {

	void (*hdmi_tx_hpd_done) (void *data);
	void *downstream_data;
	void *audio_data;

	void *feature_data[HDMI_TX_FEAT_MAX];
	struct hdmi_hdcp_ops *hdcp_ops;
@@ -195,6 +193,8 @@ struct hdmi_tx_ctrl {

	struct cec_ops hdmi_cec_ops;
	struct cec_cbs hdmi_cec_cbs;
	struct hdmi_audio_ops audio_ops;
	struct msm_hdmi_audio_setup_params audio_params;

	char disp_switch_name[MAX_SWITCH_NAME_SIZE];
};