text: /* Copyright (c) 2016, 2018, 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/extcon.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 mdss_io_data *io;
  	struct msm_hdmi_audio_setup_params params;
  	struct extcon_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 mdss_io_data *io;
  	struct hdmi_audio_acr acr;
  	struct msm_hdmi_audio_setup_params *params;
  	u32 pclk, layout, multiplier = 1, 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 = params->num_of_channels == AUDIO_CHANNEL_2 ? 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);
  
  	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);
  
  	/* N_MULTIPLE(multiplier) */
  	acr_pkt_ctl &= ~(7 << 16);
  	acr_pkt_ctl |= (multiplier & 0x7) << 16;
  
  	/* 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 mdss_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->params.num_of_channels == AUDIO_CHANNEL_2 ? 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;
  	}
  
  	extcon_set_state_sync(&audio->sdev, 0, 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 = extcon_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) {
  		extcon_dev_unregister(&audio->sdev);
  		kfree(ctx);
  	}
  }
  