3451 lines
97 KiB
C
3451 lines
97 KiB
C
/* Copyright (c) 2013-2014, 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.
|
|
*/
|
|
|
|
#include <linux/tsc.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h> /* Device drivers need this */
|
|
#include <linux/cdev.h> /* Char device drivers need that */
|
|
#include <linux/kernel.h> /* for KERN_INFO */
|
|
#include <linux/fs.h>
|
|
#include <linux/completion.h> /* for completion signaling after interrupts */
|
|
#include <linux/uaccess.h> /* for copy from/to user in the ioctls */
|
|
#include <linux/msm_iommu_domains.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h> /* parsing device tree data */
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_irq.h>
|
|
#include <mach/gpio.h> /* gpios definitions */
|
|
#include <linux/pinctrl/consumer.h> /* pinctrl API */
|
|
#include <linux/clk.h>
|
|
#include <linux/wait.h> /* wait() macros, sleeping */
|
|
#include <linux/sched.h> /* Externally defined globals */
|
|
#include <linux/poll.h> /* poll() file op */
|
|
#include <linux/io.h> /* IO macros */
|
|
#include <linux/bitops.h>
|
|
#include <linux/msm_ion.h> /* ion_map_iommu */
|
|
#include <linux/iommu.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/slab.h> /* kfree, kzalloc */
|
|
#include <linux/debugfs.h> /* debugfs support */
|
|
#include <linux/pm_runtime.h> /* debugfs support */
|
|
#include <linux/pm_wakeup.h> /* debugfs support */
|
|
#include <linux/regulator/consumer.h> /* gdsc */
|
|
#include <linux/msm-bus.h> /* bus client */
|
|
#include <linux/delay.h> /* usleep function */
|
|
/* TODO: include <linux/mpq_standby_if.h> after MCU is mainlined */
|
|
|
|
/*
|
|
* General defines
|
|
*/
|
|
#define TEST_BIT(pos, number) (number & (1 << pos))
|
|
#define CLEAR_BIT(pos, number) (number &= ~(1 << pos))
|
|
#define SET_BIT(pos, number) (number |= 1 << pos)
|
|
|
|
/*
|
|
* extract bits [@b0:@b1] (inclusive) from the value @x
|
|
* it should be @b0 <= @b1, or result is incorrect
|
|
*/
|
|
static inline u32 GETL_BITS(u32 x, int b0, int b1)
|
|
{
|
|
return (x >> b0) & ((1 << (b1 - b0 + 1)) - 1);
|
|
}
|
|
|
|
/* Bypass VBIF/IOMMU for debug and bring-up purposes */
|
|
static int tsc_iommu_bypass; /* defualt=0 using iommu */
|
|
module_param(tsc_iommu_bypass, int, S_IRUGO | S_IWUSR | S_IWGRP);
|
|
|
|
/* The rate of the clock that control TS from TSC to the CAM */
|
|
#define CICAM_CLK_RATE_12MHZ 12000000
|
|
#define CICAM_CLK_RATE_9MHZ 8971962
|
|
#define CICAM_CLK_RATE_7MHZ 7218045
|
|
/* Rates for TSC serial and parallel clocks */
|
|
#define TSC_SER_CLK_RATE 192000000
|
|
#define TSC_PAR_CLK_RATE 24000000
|
|
|
|
/* CICAM address space according to CI specification */
|
|
#define CICAM_MAX_ADDRESS 3
|
|
|
|
/*
|
|
* TSC register offsets
|
|
*/
|
|
#define TSC_HW_VERSION (0x0)
|
|
#define TSC_MUX_CFG (0x4) /* Muxs config */
|
|
#define TSC_IN_IFC_EXT (0x8) /* External demods tsifs */
|
|
#define TSC_IN_IFC_CFG_INT (0xc) /* internal demods and
|
|
cicam tsif config */
|
|
#define TSC_FSM_STATE (0x50) /* Read FSM state */
|
|
#define TSC_FSM_STATE_MASK (0x54) /* Config FSM state */
|
|
#define TSC_CAM_CMD (0x1000)/* Config cam commands */
|
|
#define TSC_CAM_RD_DATA (0x1004)/* read data for single-mode
|
|
byte */
|
|
#define TSC_STAT (0x1008)/* Interrupts status */
|
|
#define TSC_IRQ_ENA (0x100C)/* Enable interrupts */
|
|
#define TSC_IRQ_CLR (0x1010)/* Clear interrupts */
|
|
#define TSC_CIP_CFG (0x1014)/* Enable HW polling */
|
|
#define TSC_CD_STAT (0x1020)/* Card pins status */
|
|
#define TSC_RD_BUFF_ADDR (0x1024)/* Vbif address for read
|
|
buffer */
|
|
#define TSC_WR_BUFF_ADDR (0x1028)/* Vbif address for write
|
|
buffer */
|
|
#define TSC_FALSE_CD (0x102C)/* Counter of false card
|
|
detection */
|
|
#define TSC_FALSE_CD_CLR (0x1030)/* Clear false cd counter */
|
|
#define TSC_RESP_ERR (0x1034)/* State of read/write buffer
|
|
error */
|
|
#define TSC_CICAM_TSIF (0x1038)/* Enable tsif (tsc->cam) */
|
|
|
|
|
|
/*
|
|
* Registers structure definitions
|
|
*/
|
|
|
|
/* TSC_MUX_CFG */
|
|
#define MUX_EXTERNAL_DEMOD_0 0
|
|
#define MUX_EXTERNAL_DEMOD_1 1
|
|
#define MUX_INTERNAL_DEMOD 2
|
|
#define MUX_CICAM 3
|
|
#define MUX0_OFFS 0
|
|
#define MUX1_OFFS 2
|
|
#define MUX_CAM_OFFS 4
|
|
|
|
/* TSC_IN_IFC_EXT and TSC_IN_IFC_CFG_INT*/
|
|
#define TSIF_INPUT_ENABLE 0
|
|
#define TSIF_INPUT_DISABLE 1
|
|
|
|
#define TSIF_CLK_POL_OFFS 0
|
|
#define TSIF_DATA_POL_OFFS 1
|
|
#define TSIF_START_POL_OFFS 2
|
|
#define TSIF_VALID_POL_OFFS 3
|
|
#define TSIF_ERROR_POL_OFFS 4
|
|
#define TSIF_SER_PAR_OFFS 5
|
|
#define TSIF_REC_MODE_OFFS 6
|
|
#define TSIF_DATA_SWAP_OFFS 8
|
|
#define TSIF_DISABLE_OFFS 9
|
|
#define TSIF_ERR_INSERT_OFFS 10
|
|
|
|
/* TSC_FSM_STATE and TSC_FSM_STATE_MASK*/
|
|
#define FSM_STATE_BUFFER_BEG 0
|
|
#define FSM_STATE_BUFFER_END 3
|
|
#define FSM_STATE_POLL_BEG 8
|
|
#define FSM_STATE_POLL_END 10
|
|
#define FSM_STATE_BYTE_BEG 12
|
|
#define FSM_STATE_BYTE_END 13
|
|
#define FSM_STATE_MEM_WR_BEG 16
|
|
#define FSM_STATE_MEM_WR_END 17
|
|
#define FSM_STATE_MEM_RD_BEG 20
|
|
#define FSM_STATE_MEM_RD_END 21
|
|
#define FSM_STATE_IO_RD_BEG 24
|
|
#define FSM_STATE_IO_RD_END 25
|
|
#define FSM_STATE_IO_WR_BEG 28
|
|
#define FSM_STATE_IO_WR_END 29
|
|
|
|
/* TSC_CAM_CMD */
|
|
#define MEMORY_TRANSACTION 0
|
|
#define IO_TRANSACTION 1
|
|
#define WRITE_TRANSACTION 0
|
|
#define READ_TRANSACTION 1
|
|
#define SINGLE_BYTE_MODE 0
|
|
#define BUFFER_MODE 1
|
|
|
|
#define CAM_CMD_ADDR_SIZE_OFFS 0
|
|
#define CAM_CMD_WR_DATA_OFFS 16
|
|
#define CAM_CMD_IO_MEM_OFFS 24
|
|
#define CAM_CMD_RD_WR_OFFS 25
|
|
#define CAM_CMD_BUFF_MODE_OFFS 26
|
|
#define CAM_CMD_ABORT 27
|
|
|
|
/* TSC_STAT, TSC_IRQ_ENA and TSC_IRQ_CLR */
|
|
#define CAM_IRQ_EOT_OFFS 0
|
|
#define CAM_IRQ_POLL_OFFS 1
|
|
#define CAM_IRQ_RATE_MISMATCH_OFFS 2
|
|
#define CAM_IRQ_ERR_OFFS 3
|
|
#define CAM_IRQ_ABORTED_OFFS 4
|
|
|
|
/* TSC_CD_STAT */
|
|
#define TSC_CD_STAT_INSERT 0x00
|
|
#define TSC_CD_STAT_ERROR1 0x01
|
|
#define TSC_CD_STAT_ERROR2 0x02
|
|
#define TSC_CD_STAT_REMOVE 0x03
|
|
|
|
#define TSC_CD_BEG 0
|
|
#define TSC_CD_END 1
|
|
|
|
/* TSC_CICAM_TSIF */
|
|
#define TSC_CICAM_TSIF_OE_OFFS 0
|
|
|
|
/* Data structures */
|
|
|
|
/**
|
|
* enum transaction_state - states for the transacation interrupt reason
|
|
*/
|
|
enum transaction_state {
|
|
BEFORE_TRANSACTION = 0,
|
|
TRANSACTION_SUCCESS = 1,
|
|
TRANSACTION_ERROR = -1,
|
|
TRANSACTION_CARD_REMOVED = -2
|
|
};
|
|
|
|
/**
|
|
* enum pcmcia_state - states for the pcmcia pinctrl states
|
|
* Note: the numbers here corresponds to the numbers of enum tsc_cam_personality
|
|
* in tsc.h file.
|
|
*/
|
|
enum pcmcia_state {
|
|
PCMCIA_STATE_DISABLE = 0,
|
|
PCMCIA_STATE_CI_CARD = 1,
|
|
PCMCIA_STATE_CI_PLUS = 2,
|
|
PCMCIA_STATE_PC_CARD = 3
|
|
};
|
|
|
|
/**
|
|
* struct iommu_info - manage all the iommu information
|
|
*
|
|
* @group: TSC IOMMU group.
|
|
* @domain: TSC IOMMU domain.
|
|
* @domain_num: TSC IOMMU domain number.
|
|
* @partition_num: TSC iommu partition number.
|
|
* @ion_client: TSC IOMMU client.
|
|
* @iommu_group_name TSC IOMMU group name.
|
|
*/
|
|
struct iommu_info {
|
|
struct iommu_group *group;
|
|
struct iommu_domain *domain;
|
|
int domain_num;
|
|
int partition_num;
|
|
struct ion_client *ion_client;
|
|
const char *iommu_group_name;
|
|
};
|
|
|
|
/**
|
|
* struct pinctrl_current_state - represent which TLMM pins currently active
|
|
*
|
|
* @ts0: true if TS-in 0 is active, false otherwise.
|
|
* @ts1: true if TS-in 1 is active, false otherwise.
|
|
* @pcmcia_state: Represent the pcmcia pins state.
|
|
*/
|
|
struct pinctrl_current_state {
|
|
bool ts0;
|
|
bool ts1;
|
|
enum pcmcia_state pcmcia_state;
|
|
};
|
|
/**
|
|
* struct pinctrl_info - manage all the pinctrl information
|
|
*
|
|
* @pinctrl: TSC pinctrl state holder.
|
|
* @disable: pinctrl state to disable all the pins.
|
|
* @ts0: pinctrl state to activate TS-in 0 alone.
|
|
* @ts1: pinctrl state to activate TS-in 1 alone.
|
|
* @dual_ts: pinctrl state to activate both TS-in.
|
|
* @pc_card: pinctrl state to activate pcmcia upon card insertion.
|
|
* @ci_card: pinctrl state to activate pcmcia after personality
|
|
* change to CI card.
|
|
* @ci_plus: pinctrl state to activate pcmcia after personality
|
|
* change to CI+ card.
|
|
* @ts0_pc_card: pinctrl state to activate TS-in 0 and pcmcia upon card
|
|
* insertion.
|
|
* @ts0_ci_card: pinctrl state to activate TS-in 0 and pcmcia after
|
|
* personality change to CI card.
|
|
* @ts0_ci_plus: pinctrl state to activate TS-in 0 and pcmcia after
|
|
* personality change to CI+ card.
|
|
* @ts1_pc_card: pinctrl state to activate TS-in 1 and pcmcia upon card
|
|
* insertion.
|
|
* @ts1_ci_card: pinctrl state to activate TS-in 1 and pcmcia after
|
|
* personality change to CI card.
|
|
* @ts1_ci_plus: pinctrl state to activate TS-in 1 and pcmcia after
|
|
* personality change to CI+ card.
|
|
* @dual_ts_pc_card: pinctrl state to activate both TS-in and pcmcia upon
|
|
* card insertion.
|
|
* @dual_ts_ci_card: pinctrl state to activate both TS-in and pcmcia after
|
|
* personality change to CI card.
|
|
* @dual_ts_ci_plus: pinctrl state to activate both TS-in and pcmcia after
|
|
* personality change to CI+ card.
|
|
* @is_ts0: true if ts0 pinctrl states exist in device tree, false
|
|
* otherwise.
|
|
* @is_ts1: true if ts1 pinctrl states exist in device tree, false
|
|
* otherwise.
|
|
* @is_pcmcia: true if pcmcia pinctrl states exist in device tree,
|
|
* false otherwise.
|
|
* @curr_state: the current state of the TLMM pins.
|
|
*/
|
|
struct pinctrl_info {
|
|
struct pinctrl *pinctrl;
|
|
struct pinctrl_state *disable;
|
|
struct pinctrl_state *ts0;
|
|
struct pinctrl_state *ts1;
|
|
struct pinctrl_state *dual_ts;
|
|
struct pinctrl_state *pc_card;
|
|
struct pinctrl_state *ci_card;
|
|
struct pinctrl_state *ci_plus;
|
|
struct pinctrl_state *ts0_pc_card;
|
|
struct pinctrl_state *ts0_ci_card;
|
|
struct pinctrl_state *ts0_ci_plus;
|
|
struct pinctrl_state *ts1_pc_card;
|
|
struct pinctrl_state *ts1_ci_card;
|
|
struct pinctrl_state *ts1_ci_plus;
|
|
struct pinctrl_state *dual_ts_pc_card;
|
|
struct pinctrl_state *dual_ts_ci_card;
|
|
struct pinctrl_state *dual_ts_ci_plus;
|
|
bool is_ts0;
|
|
bool is_ts1;
|
|
bool is_pcmcia;
|
|
struct pinctrl_current_state curr_state;
|
|
};
|
|
|
|
/**
|
|
* struct tsc_mux_chdev - TSC Mux character device
|
|
*
|
|
* @cdev: TSC Mux cdev.
|
|
* @mutex: A mutex for mutual exclusion between Mux API calls.
|
|
* @poll_queue: Waiting queue for rate mismatch interrupt.
|
|
* @spinlock: A spinlock to protect accesses to
|
|
* data structures that happen from APIs and ISRs.
|
|
* @rate_interrupt: A flag indicating if rate mismatch interrupt received.
|
|
*/
|
|
struct tsc_mux_chdev {
|
|
struct cdev cdev;
|
|
struct mutex mutex;
|
|
wait_queue_head_t poll_queue;
|
|
spinlock_t spinlock;
|
|
bool rate_interrupt;
|
|
};
|
|
|
|
/**
|
|
* struct tsc_ci_chdev - TSC CI character device
|
|
*
|
|
* @cdev: TSC CI cdev.
|
|
* @mutex: A mutex for mutual exclusion between CI API calls.
|
|
* @poll_queue: Waiting queue for card detection interrupt.
|
|
* @spinlock: A spinlock to protect accesses to data structures that
|
|
* happen from APIs and ISRs.
|
|
* @transaction_complete: A completion struct indicating end of data
|
|
* transaction.
|
|
* @transaction_finish: A completion struct indicating data transaction func
|
|
* has finished.
|
|
* @transaction_state: flag indicating the reason for transaction end.
|
|
* @ci_card_status: The last card status received by the upper layer.
|
|
* @data_busy: true when the device is in the middle of data
|
|
* transaction operation, false otherwise.
|
|
*/
|
|
struct tsc_ci_chdev {
|
|
struct cdev cdev;
|
|
struct mutex mutex;
|
|
wait_queue_head_t poll_queue;
|
|
spinlock_t spinlock;
|
|
struct completion transaction_complete;
|
|
struct completion transaction_finish;
|
|
enum transaction_state transaction_state;
|
|
enum tsc_card_status card_status;
|
|
bool data_busy;
|
|
};
|
|
|
|
/**
|
|
* struct tsc_device - TSC device
|
|
*
|
|
* @pdev: TSC platform device.
|
|
* @device_mux: Mux device for sysfs and /dev entry.
|
|
* @device_ci: CI device for sysfs and /dev entry.
|
|
* @mux_chdev: TSC Mux character device instance.
|
|
* @ci_chdev: TSC CI character device instance.
|
|
* @mux_device_number: TSC Mux major number.
|
|
* @ci_device_number: TSC CI major number.
|
|
* @num_mux_opened: A counter to ensure 1 TSC Mux character device.
|
|
* @num_ci_opened: A counter to ensure 1 TSC CI character device.
|
|
* @num_device_open: A counter to synch init of power and bus voting.
|
|
* @mutex: Global mutex to to synch init of power and bus voting.
|
|
* @base: Base memory address for the TSC registers.
|
|
* @card_detection_irq: Interrupt No. of the card detection interrupt.
|
|
* @cam_cmd_irq: Interrupt No. of the cam cmd interrupt.
|
|
* @iommu_info: TSC IOMMU parameters.
|
|
* @ahb_clk: The clock for accessing the TSC registers.
|
|
* @ci_clk: The clock for TSC internal logic.
|
|
* @ser_clk: The clock for synchronizing serial TS input.
|
|
* @par_clk: The clock for synchronizing parallel TS input.
|
|
* @cicam_ts_clk: The clock for pushing TS data into the cicam.
|
|
* @tspp2_core_clk: The clock for enabling the TSPP2.
|
|
* @vbif_tspp2_clk: The clock for accessing the VBIF.
|
|
* @vbif_ahb_clk: The clock for VBIF AHB.
|
|
* @vbif_axi_clk: The clock for VBIF AXI.
|
|
* @gdsc: The Broadcast GDSC.
|
|
* @bus_client: The TSC bus client.
|
|
* @pinctrl_info: TSC pinctrl parameters.
|
|
* @reset_cam_gpio: GPIO No. for CAM HW reset.
|
|
* @hw_card_status: The card status as reflected by the HW registers.
|
|
* @card_power: True if the card is powered up, false otherwise.
|
|
* @debugfs_entry: TSC device debugfs entry.
|
|
*/
|
|
struct tsc_device {
|
|
struct platform_device *pdev;
|
|
struct device *device_mux;
|
|
struct device *device_ci;
|
|
struct tsc_mux_chdev mux_chdev;
|
|
struct tsc_ci_chdev ci_chdev;
|
|
dev_t mux_device_number;
|
|
dev_t ci_device_number;
|
|
int num_mux_opened;
|
|
int num_ci_opened;
|
|
int num_device_open;
|
|
struct mutex mutex;
|
|
void __iomem *base;
|
|
unsigned int card_detection_irq;
|
|
unsigned int cam_cmd_irq;
|
|
struct iommu_info iommu_info;
|
|
struct clk *ahb_clk;
|
|
struct clk *ci_clk;
|
|
struct clk *ser_clk;
|
|
struct clk *par_clk;
|
|
struct clk *cicam_ts_clk;
|
|
struct clk *tspp2_core_clk;
|
|
struct clk *vbif_tspp2_clk;
|
|
struct clk *vbif_ahb_clk;
|
|
struct clk *vbif_axi_clk;
|
|
struct regulator *gdsc;
|
|
uint32_t bus_client;
|
|
struct pinctrl_info pinctrl_info;
|
|
int reset_cam_gpio;
|
|
enum tsc_card_status hw_card_status;
|
|
bool card_power;
|
|
struct dentry *debugfs_entry;
|
|
};
|
|
|
|
/* Global TSC device class */
|
|
static struct class *tsc_class;
|
|
|
|
/* Global TSC device database */
|
|
static struct tsc_device *tsc_device;
|
|
|
|
/************************** Debugfs Support **************************/
|
|
/* debugfs entries */
|
|
#define TSC_S_RW (S_IRUGO | S_IWUSR)
|
|
|
|
struct debugfs_entry {
|
|
const char *name;
|
|
mode_t mode;
|
|
int offset;
|
|
};
|
|
|
|
static const struct debugfs_entry tsc_regs_32[] = {
|
|
{"tsc_hw_version", S_IRUGO, TSC_HW_VERSION},
|
|
{"tsc_mux", TSC_S_RW, TSC_MUX_CFG},
|
|
{"tsif_external_demods", TSC_S_RW, TSC_IN_IFC_EXT},
|
|
{"tsif_internal_demod_cam", TSC_S_RW, TSC_IN_IFC_CFG_INT},
|
|
{"tsc_fsm_state", S_IRUGO, TSC_FSM_STATE},
|
|
{"tsc_fsm_state_mask", TSC_S_RW, TSC_FSM_STATE_MASK},
|
|
{"tsc_cam_cmd", TSC_S_RW, TSC_CAM_CMD},
|
|
{"tsc_rd_buff_addr", TSC_S_RW, TSC_RD_BUFF_ADDR},
|
|
{"tsc_wr_buff_addr", TSC_S_RW, TSC_WR_BUFF_ADDR},
|
|
};
|
|
|
|
static const struct debugfs_entry tsc_regs_16[] = {
|
|
{"tsc_false_cd_counter", S_IRUGO, TSC_FALSE_CD},
|
|
{"tsc_cicam_tsif", TSC_S_RW, TSC_CICAM_TSIF},
|
|
};
|
|
|
|
static const struct debugfs_entry tsc_regs_8[] = {
|
|
{"tsc_cam_rd_data", S_IRUGO, TSC_CAM_RD_DATA},
|
|
{"tsc_irq_stat", S_IRUGO, TSC_STAT},
|
|
{"tsc_irq_ena", TSC_S_RW, TSC_IRQ_ENA},
|
|
{"tsc_irq_clr", TSC_S_RW, TSC_IRQ_CLR},
|
|
{"tsc_ena_hw_poll", TSC_S_RW, TSC_CIP_CFG},
|
|
{"tsc_card_stat", TSC_S_RW, TSC_CD_STAT},
|
|
{"tsc_false_cd_counter_clr", TSC_S_RW, TSC_FALSE_CD_CLR},
|
|
{"tsc_last_error_resp", S_IRUGO, TSC_RESP_ERR},
|
|
};
|
|
|
|
/* debugfs settings */
|
|
static int debugfs_iomem_set(void *data, u64 val)
|
|
{
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (!tsc_device->num_device_open) {
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -ENXIO;
|
|
}
|
|
|
|
mutex_unlock(&tsc_device->mutex);
|
|
|
|
writel_relaxed(val, data);
|
|
wmb();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int debugfs_iomem_get(void *data, u64 *val)
|
|
{
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (!tsc_device->num_device_open) {
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -ENXIO;
|
|
}
|
|
|
|
mutex_unlock(&tsc_device->mutex);
|
|
|
|
*val = readl_relaxed(data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x32, debugfs_iomem_get,
|
|
debugfs_iomem_set, "0x%08llX");
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x16, debugfs_iomem_get,
|
|
debugfs_iomem_set, "0x%04llX");
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_iomem_x8, debugfs_iomem_get,
|
|
debugfs_iomem_set, "0x%02llX");
|
|
|
|
/**
|
|
* tsc_debugfs_init() - TSC device debugfs initialization.
|
|
*/
|
|
static void tsc_debugfs_init(void)
|
|
{
|
|
int i;
|
|
struct dentry *dentry;
|
|
void __iomem *base = tsc_device->base;
|
|
|
|
tsc_device->debugfs_entry = debugfs_create_dir("tsc", NULL);
|
|
if (!tsc_device->debugfs_entry)
|
|
return;
|
|
dentry = debugfs_create_dir("regs", tsc_device->debugfs_entry);
|
|
if (dentry) {
|
|
for (i = 0; i < ARRAY_SIZE(tsc_regs_32); i++) {
|
|
debugfs_create_file(
|
|
tsc_regs_32[i].name,
|
|
tsc_regs_32[i].mode,
|
|
dentry,
|
|
base + tsc_regs_32[i].offset,
|
|
&fops_iomem_x32);
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(tsc_regs_16); i++) {
|
|
debugfs_create_file(
|
|
tsc_regs_16[i].name,
|
|
tsc_regs_16[i].mode,
|
|
dentry,
|
|
base + tsc_regs_16[i].offset,
|
|
&fops_iomem_x16);
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(tsc_regs_8); i++) {
|
|
debugfs_create_file(
|
|
tsc_regs_8[i].name,
|
|
tsc_regs_8[i].mode,
|
|
dentry,
|
|
base + tsc_regs_8[i].offset,
|
|
&fops_iomem_x8);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* tsc_debugfs_exit() - TSC device debugfs teardown.
|
|
*/
|
|
static void tsc_debugfs_exit(void)
|
|
{
|
|
debugfs_remove_recursive(tsc_device->debugfs_entry);
|
|
tsc_device->debugfs_entry = NULL;
|
|
}
|
|
|
|
/**
|
|
* tsc_update_hw_card_status() - Update the hw_status according to the HW reg.
|
|
*
|
|
* Read the register indicating the card status (inserted, removed, error) and
|
|
* update the tsc_device->hw_card_status accordingly.
|
|
*/
|
|
static void tsc_update_hw_card_status(void)
|
|
{
|
|
u32 cd_reg, card_status = 0;
|
|
|
|
cd_reg = readl_relaxed(tsc_device->base + TSC_CD_STAT);
|
|
card_status = GETL_BITS(cd_reg, TSC_CD_BEG, TSC_CD_END);
|
|
switch (card_status) {
|
|
case TSC_CD_STAT_INSERT:
|
|
tsc_device->hw_card_status = TSC_CARD_STATUS_DETECTED;
|
|
break;
|
|
case TSC_CD_STAT_ERROR1:
|
|
case TSC_CD_STAT_ERROR2:
|
|
tsc_device->hw_card_status = TSC_CARD_STATUS_FAILURE;
|
|
break;
|
|
case TSC_CD_STAT_REMOVE:
|
|
tsc_device->hw_card_status = TSC_CARD_STATUS_NOT_DETECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* tsc_card_power_down() - power down card interface upon removal.
|
|
*
|
|
* Power down the card by disable VPP, disable pins in the TLMM, assert the
|
|
* reset line and disable the level-shifters. This function assumes the spinlock
|
|
* of ci device is already taken.
|
|
*
|
|
* Return 0 on finish, error value if interrupted while acquiring a mutex.
|
|
*/
|
|
static int tsc_card_power_down(void)
|
|
{
|
|
int ret = 0;
|
|
struct pinctrl_info *ppinctrl = &tsc_device->pinctrl_info;
|
|
struct pinctrl_current_state *pcurr_state = &ppinctrl->curr_state;
|
|
int reset_gpio = tsc_device->reset_cam_gpio;
|
|
u32 reg = 0;
|
|
|
|
/* Clearing CAM TSIF OE to disable I/O CAM transactions */
|
|
CLEAR_BIT(TSC_CICAM_TSIF_OE_OFFS, reg);
|
|
writel_relaxed(reg, tsc_device->base + TSC_CICAM_TSIF);
|
|
|
|
/* Assert the reset line */
|
|
ret = gpio_direction_output(reset_gpio, 1); /* assert */
|
|
if (ret != 0)
|
|
pr_err("%s: Failed to assert the reset CAM GPIO\n", __func__);
|
|
|
|
/* Disable all the level-shifters */
|
|
/* TODO: call mpq_standby_pcmcia_master0_set(0) after MCU mainlined */
|
|
if (ret != 0)
|
|
pr_err("%s: error disable master0 level-shifters. ret value = %d\n",
|
|
__func__, ret);
|
|
/* TODO: call mpq_standby_pcmcia_master1_set(1) after MCU mainlined */
|
|
if (ret != 0)
|
|
pr_err("%s: error disable master1 level-shifters. ret value = %d\n",
|
|
__func__, ret);
|
|
|
|
/* Power-down the card */
|
|
/* TODO: call mpq_standby_pcmcia_vpp_set(1) after MCU mainlined */
|
|
if (ret != 0)
|
|
pr_err("%s: error disabling VPP. ret value = %d\n", __func__,
|
|
ret);
|
|
/* Wait 10msec until VPP become stable */
|
|
usleep(10000);
|
|
|
|
/* Disable pins in the TLMM */
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts);
|
|
else if (pcurr_state->ts0 && !pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0);
|
|
else if (!pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->disable);
|
|
if (ret != 0)
|
|
pr_err("%s: error changing PCMCIA pins upon card removal. ret value = %d\n",
|
|
__func__, ret);
|
|
else
|
|
pcurr_state->pcmcia_state = PCMCIA_STATE_DISABLE;
|
|
|
|
mutex_unlock(&tsc_device->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_card_power_up() - power up card interface upon insertion.
|
|
*
|
|
* Power up the card by open VPP, enable pins in the TLMM, deassert the reset
|
|
* line and enable the level-shifters. This function assumes the spinlock of ci
|
|
* device is already taken.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_card_power_up(void)
|
|
{
|
|
int ret = 0;
|
|
struct pinctrl_info *ppinctrl = &tsc_device->pinctrl_info;
|
|
struct pinctrl_current_state *pcurr_state = &ppinctrl->curr_state;
|
|
int reset_gpio = tsc_device->reset_cam_gpio;
|
|
|
|
/* Power-up the card */
|
|
/* TODO: call mpq_standby_pcmcia_vpp_set(1) after MCU mainlined */
|
|
if (ret != 0) {
|
|
pr_err("%s: error setting VPP. ret value = %d\n", __func__,
|
|
ret);
|
|
return ret;
|
|
}
|
|
/* Wait 10msec until VPP become stable */
|
|
usleep(10000);
|
|
|
|
/* Enable pins in the TLMM */
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_pc_card);
|
|
else if (pcurr_state->ts0 && !pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_pc_card);
|
|
else if (!pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_pc_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->pc_card);
|
|
if (ret != 0) {
|
|
pr_err("%s: error changing PCMCIA pins upon card insertion. ret value = %d\n",
|
|
__func__, ret);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
goto err;
|
|
} else {
|
|
pcurr_state->pcmcia_state = PCMCIA_STATE_PC_CARD;
|
|
}
|
|
mutex_unlock(&tsc_device->mutex);
|
|
|
|
/* Release the reset line */
|
|
ret = gpio_direction_output(reset_gpio, 0); /* Deassert */
|
|
if (ret != 0) {
|
|
pr_err("%s: Failed to deassert the reset CAM GPIO\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
/* Enable level-shifters for all pins */
|
|
/* TODO: call mpq_standby_pcmcia_master0_set(0) after MCU mainlined */
|
|
if (ret != 0) {
|
|
pr_err("%s: error setting master0 level-shifters. ret value = %d\n",
|
|
__func__, ret);
|
|
goto err;
|
|
}
|
|
/* TODO: call mpq_standby_pcmcia_master1_set(0) after MCU mainlined */
|
|
if (ret != 0) {
|
|
pr_err("%s: error setting master1 level-shifters. ret value = %d\n",
|
|
__func__, ret);
|
|
goto err;
|
|
}
|
|
|
|
/* Wait 20msec at the end of the power-up sequence */
|
|
usleep(20000);
|
|
|
|
return ret;
|
|
|
|
err:
|
|
tsc_card_power_down();
|
|
return ret;
|
|
}
|
|
|
|
/************************** Interrupt handlers **************************/
|
|
/**
|
|
* tsc_card_detect_irq_thread_handler() - TSC card detect interrupt handler.
|
|
*
|
|
* @irq: Interrupt number.
|
|
* @dev: TSC device.
|
|
*
|
|
* The handler is executed on a thread context, not in the interrupt context
|
|
* (can take a mutex and sleep).
|
|
* Read the card detection status from the register and initiate a power-up/down
|
|
* sequence accordingly. The sequence will occur only if a change is needed in
|
|
* the current power state.
|
|
*
|
|
*/
|
|
static irqreturn_t tsc_card_detect_irq_thread_handler(int irq, void *dev)
|
|
{
|
|
int ret = 0;
|
|
struct tsc_ci_chdev *tsc_ci;
|
|
unsigned long flags = 0;
|
|
|
|
tsc_ci = &tsc_device->ci_chdev;
|
|
|
|
mutex_lock(&tsc_ci->mutex);
|
|
|
|
tsc_update_hw_card_status();
|
|
|
|
/* waking-up ci poll queue */
|
|
wake_up_interruptible(&tsc_ci->poll_queue);
|
|
|
|
/* If in the middle of a data transaction- aborting the transaction */
|
|
if (tsc_ci->data_busy && tsc_device->hw_card_status ==
|
|
TSC_CARD_STATUS_NOT_DETECTED) {
|
|
spin_lock_irqsave(&tsc_ci->spinlock, flags);
|
|
tsc_ci->transaction_state = TRANSACTION_CARD_REMOVED;
|
|
spin_unlock_irqrestore(&tsc_ci->spinlock, flags);
|
|
complete_all(&tsc_ci->transaction_complete);
|
|
}
|
|
|
|
if (tsc_device->hw_card_status == TSC_CARD_STATUS_DETECTED &&
|
|
!tsc_device->card_power) {
|
|
ret = tsc_card_power_up();
|
|
if (ret != 0)
|
|
pr_err("%s: card power-up failed\n", __func__);
|
|
else
|
|
tsc_device->card_power = true;
|
|
} else if (tsc_device->hw_card_status == TSC_CARD_STATUS_NOT_DETECTED &&
|
|
tsc_device->card_power) {
|
|
tsc_card_power_down();
|
|
/*
|
|
* In case something failed during the power down, the sequence
|
|
* continue and the status of the card power is considered as
|
|
* powered down.
|
|
*/
|
|
tsc_device->card_power = false;
|
|
}
|
|
|
|
mutex_unlock(&tsc_ci->mutex);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* tsc_cam_cmd_irq_handler() - TSC CAM interrupt handler.
|
|
*
|
|
* @irq: Interrupt number.
|
|
* @dev: TSC device.
|
|
*
|
|
* Handle TSC CAM HW interrupt. Handle the CAM transaction interrupts by waking
|
|
* up the completion sync object, handle rate mismatch interrupt by waking-up
|
|
* the TSC Mux poll wait-queue and clear the interrupts received.
|
|
*
|
|
* Return IRQ_HANDLED.
|
|
*/
|
|
static irqreturn_t tsc_cam_cmd_irq_handler(int irq, void *dev)
|
|
{
|
|
struct tsc_ci_chdev *tsc_ci;
|
|
struct tsc_mux_chdev *tsc_mux;
|
|
unsigned long flags;
|
|
u32 stat_reg, ena_reg;
|
|
|
|
tsc_ci = &tsc_device->ci_chdev;
|
|
tsc_mux = &tsc_device->mux_chdev;
|
|
|
|
stat_reg = readl_relaxed(tsc_device->base + TSC_STAT);
|
|
|
|
/* Handling transaction interrupts */
|
|
if (TEST_BIT(CAM_IRQ_ERR_OFFS, stat_reg) ||
|
|
TEST_BIT(CAM_IRQ_EOT_OFFS, stat_reg)) {
|
|
spin_lock_irqsave(&tsc_ci->spinlock, flags);
|
|
|
|
if (TEST_BIT(CAM_IRQ_EOT_OFFS, stat_reg))
|
|
tsc_ci->transaction_state = TRANSACTION_SUCCESS;
|
|
else
|
|
tsc_ci->transaction_state = TRANSACTION_ERROR;
|
|
|
|
spin_unlock_irqrestore(&tsc_ci->spinlock, flags);
|
|
complete_all(&tsc_ci->transaction_complete);
|
|
}
|
|
|
|
/* Handling rate mismatch interrupt */
|
|
if (TEST_BIT(CAM_IRQ_RATE_MISMATCH_OFFS, stat_reg)) {
|
|
spin_lock_irqsave(&tsc_mux->spinlock, flags);
|
|
|
|
/* Disabling rate mismatch interrupt */
|
|
ena_reg = readl_relaxed(tsc_device->base + TSC_IRQ_ENA);
|
|
CLEAR_BIT(CAM_IRQ_RATE_MISMATCH_OFFS, ena_reg);
|
|
writel_relaxed(ena_reg, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
/* Setting internal flag for poll */
|
|
tsc_mux->rate_interrupt = true;
|
|
|
|
spin_unlock_irqrestore(&tsc_mux->spinlock, flags);
|
|
/* waking-up mux poll queue */
|
|
wake_up_interruptible(&tsc_mux->poll_queue);
|
|
}
|
|
|
|
/* Clearing all the interrupts received */
|
|
writel_relaxed(stat_reg, tsc_device->base + TSC_IRQ_CLR);
|
|
|
|
/*
|
|
* Before returning IRQ_HANDLED to the generic interrupt handling
|
|
* framework need to make sure all operations including clearing of
|
|
* interrupt status registers in the hardware is performed.
|
|
* Thus a barrier after clearing the interrupt status register
|
|
* is required to guarantee that the interrupt status register has
|
|
* really been cleared by the time we return from this handler.
|
|
*/
|
|
wmb();
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/************************** Internal functions **************************/
|
|
|
|
/**
|
|
* tsc_set_cicam_clk() - Setting the rate of the TS from the TSC to the CAM
|
|
*
|
|
* @arg: The argument received from the user-space via set rate IOCTL.
|
|
* It is the value of the requested rate in MHz.
|
|
*
|
|
* Setting the rate of the cicam_ts_clk clock, with one of the valid clock
|
|
* frequencies. The arg value given is rounded to the nearest frequency.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_set_cicam_clk(unsigned long arg)
|
|
{
|
|
int ret;
|
|
|
|
if (arg <= 8)
|
|
ret = clk_set_rate(tsc_device->cicam_ts_clk,
|
|
CICAM_CLK_RATE_7MHZ);
|
|
else if (arg <= 11)
|
|
ret = clk_set_rate(tsc_device->cicam_ts_clk,
|
|
CICAM_CLK_RATE_9MHZ);
|
|
else
|
|
ret = clk_set_rate(tsc_device->cicam_ts_clk,
|
|
CICAM_CLK_RATE_12MHZ);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_enable_rate_irq() - Enabling the rate mismatch interrupt.
|
|
*
|
|
* @tsc_mux: TSC Mux device.
|
|
*
|
|
* Setting the bit of this interrupt in the register that controls which
|
|
* interrupts are enabled.
|
|
*/
|
|
static void tsc_enable_rate_irq(struct tsc_mux_chdev *tsc_mux)
|
|
{
|
|
unsigned long flags;
|
|
u32 ena_reg = 0;
|
|
|
|
spin_lock_irqsave(&tsc_mux->spinlock, flags);
|
|
|
|
/* Setting the bit to start receiving rate mismatch interrupt again */
|
|
ena_reg = readl_relaxed(tsc_device->base + TSC_IRQ_ENA);
|
|
SET_BIT(CAM_IRQ_RATE_MISMATCH_OFFS, ena_reg);
|
|
writel_relaxed(ena_reg, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
spin_unlock_irqrestore(&tsc_mux->spinlock, flags);
|
|
}
|
|
|
|
/**
|
|
* tsc_config_tsif() - Modifying TSIF configuration.
|
|
*
|
|
* @tsc_mux: TSC Mux device.
|
|
* @tsif_params: TSIF parameters received from the user-space via IOCTL.
|
|
*
|
|
* Update the specified TSIF parameters according to the values in tsif_params.
|
|
* The update is done by modifying a HW register.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_config_tsif(struct tsc_mux_chdev *tsc_mux,
|
|
struct tsc_tsif_params *tsif_params)
|
|
{
|
|
int ret = 0;
|
|
u32 reg;
|
|
int reg_internal_offs;
|
|
u32 reg_addr_offs;
|
|
|
|
switch (tsif_params->source) {
|
|
case TSC_SOURCE_EXTERNAL0:
|
|
reg_internal_offs = 0;
|
|
reg_addr_offs = TSC_IN_IFC_EXT;
|
|
break;
|
|
case TSC_SOURCE_EXTERNAL1:
|
|
reg_internal_offs = 16;
|
|
reg_addr_offs = TSC_IN_IFC_EXT;
|
|
break;
|
|
case TSC_SOURCE_INTERNAL:
|
|
reg_internal_offs = 0;
|
|
reg_addr_offs = TSC_IN_IFC_CFG_INT;
|
|
break;
|
|
case TSC_SOURCE_CICAM:
|
|
reg_internal_offs = 16;
|
|
reg_addr_offs = TSC_IN_IFC_CFG_INT;
|
|
break;
|
|
default:
|
|
pr_err("%s: unidentified source parameter\n", __func__);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
|
|
reg = readl_relaxed(tsc_device->base + reg_addr_offs);
|
|
|
|
/* Modifying TSIF settings in the register value */
|
|
(tsif_params->clock_polarity ?
|
|
SET_BIT((reg_internal_offs + TSIF_CLK_POL_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_CLK_POL_OFFS), reg));
|
|
(tsif_params->data_polarity ?
|
|
SET_BIT(((reg_internal_offs + TSIF_DATA_POL_OFFS)), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_DATA_POL_OFFS), reg));
|
|
(tsif_params->start_polarity ?
|
|
SET_BIT((reg_internal_offs + TSIF_START_POL_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_START_POL_OFFS), reg));
|
|
(tsif_params->valid_polarity ?
|
|
SET_BIT((reg_internal_offs + TSIF_VALID_POL_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_VALID_POL_OFFS), reg));
|
|
(tsif_params->error_polarity ?
|
|
SET_BIT((reg_internal_offs + TSIF_ERROR_POL_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_ERROR_POL_OFFS), reg));
|
|
(tsif_params->data_type ?
|
|
SET_BIT((reg_internal_offs + TSIF_SER_PAR_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_SER_PAR_OFFS), reg));
|
|
reg &= ~(0x3 << TSIF_REC_MODE_OFFS);
|
|
reg |= (tsif_params->receive_mode << TSIF_REC_MODE_OFFS);
|
|
(tsif_params->data_swap ?
|
|
SET_BIT((reg_internal_offs + TSIF_DATA_SWAP_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_DATA_SWAP_OFFS), reg));
|
|
(tsif_params->set_error ?
|
|
SET_BIT((reg_internal_offs + TSIF_ERR_INSERT_OFFS), reg) :
|
|
CLEAR_BIT((reg_internal_offs + TSIF_ERR_INSERT_OFFS), reg));
|
|
|
|
/* Writing the new settings to the register */
|
|
writel_relaxed(reg, tsc_device->base + reg_addr_offs);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_suspend_ts_pins() - Suspend TS-in pins
|
|
*
|
|
* @source: The TSIF to configure.
|
|
*
|
|
* Config the TLMM pins of a TSIF as TS-in pins in sleep state according to
|
|
* the current pinctrl configuration of the other pins.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_suspend_ts_pins(enum tsc_source source)
|
|
{
|
|
int ret = 0;
|
|
struct pinctrl_info *ppinctrl = &tsc_device->pinctrl_info;
|
|
struct pinctrl_current_state *pcurr_state = &ppinctrl->curr_state;
|
|
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (source == TSC_SOURCE_EXTERNAL0) {
|
|
if (!ppinctrl->is_ts0) {
|
|
pr_err("%s: No TS0-in pinctrl definitions were found in the TSC devicetree\n",
|
|
__func__);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Transition from current pinctrl state to curr + ts0 sleep */
|
|
switch (pcurr_state->pcmcia_state) {
|
|
case PCMCIA_STATE_DISABLE:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->disable);
|
|
break;
|
|
case PCMCIA_STATE_PC_CARD:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_pc_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->pc_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_CARD:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_ci_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ci_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_PLUS:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_ci_plus);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ci_plus);
|
|
break;
|
|
}
|
|
} else { /* source == TSC_SOURCE_EXTERNAL1 */
|
|
if (!ppinctrl->is_ts1) {
|
|
pr_err("%s: No TS1-in pinctrl definitions were found in the TSC devicetree\n",
|
|
__func__);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Transition from current pinctrl state to curr + ts1 sleep */
|
|
switch (pcurr_state->pcmcia_state) {
|
|
case PCMCIA_STATE_DISABLE:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->disable);
|
|
break;
|
|
case PCMCIA_STATE_PC_CARD:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_pc_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->pc_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_CARD:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_ci_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ci_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_PLUS:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_ci_plus);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ci_plus);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
pr_err("%s: error disabling TS-in pins. ret value = %d\n",
|
|
__func__, ret);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Update the current pinctrl state in the internal struct */
|
|
if (source == TSC_SOURCE_EXTERNAL0)
|
|
pcurr_state->ts0 = false;
|
|
else
|
|
pcurr_state->ts1 = false;
|
|
|
|
mutex_unlock(&tsc_device->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_activate_ts_pins() - Activate TS-in pins
|
|
*
|
|
* @source: The TSIF to configure.
|
|
*
|
|
* Config the TLMM pins of a TSIF as TS-in pins in active state according to
|
|
* the current pinctrl configuration of the other pins
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_activate_ts_pins(enum tsc_source source)
|
|
{
|
|
int ret = 0;
|
|
struct pinctrl_info *ppinctrl = &tsc_device->pinctrl_info;
|
|
struct pinctrl_current_state *pcurr_state = &ppinctrl->curr_state;
|
|
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (source == TSC_SOURCE_EXTERNAL0) {
|
|
if (!ppinctrl->is_ts0) {
|
|
pr_err("%s: No TS0-in pinctrl definitions were found in the TSC devicetree\n",
|
|
__func__);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Transition from current pinctrl state to curr + ts0 active */
|
|
switch (pcurr_state->pcmcia_state) {
|
|
case PCMCIA_STATE_DISABLE:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0);
|
|
break;
|
|
case PCMCIA_STATE_PC_CARD:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_pc_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_pc_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_CARD:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_ci_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_ci_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_PLUS:
|
|
if (pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_ci_plus);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_ci_plus);
|
|
break;
|
|
}
|
|
} else { /* source == TSC_SOURCE_EXTERNAL1 */
|
|
if (!ppinctrl->is_ts1) {
|
|
pr_err("%s: No TS1-in pinctrl definitions were found in the TSC devicetree\n",
|
|
__func__);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Transition from current pinctrl state to curr + ts1 active */
|
|
switch (pcurr_state->pcmcia_state) {
|
|
case PCMCIA_STATE_DISABLE:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1);
|
|
break;
|
|
case PCMCIA_STATE_PC_CARD:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_pc_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_pc_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_CARD:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_ci_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_ci_card);
|
|
break;
|
|
case PCMCIA_STATE_CI_PLUS:
|
|
if (pcurr_state->ts0)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_ci_plus);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_ci_plus);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret != 0) {
|
|
pr_err("%s: error activating TS-in pins. ret value = %d\n",
|
|
__func__, ret);
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Update the current pinctrl state in the internal struct */
|
|
if (source == TSC_SOURCE_EXTERNAL0)
|
|
pcurr_state->ts0 = true;
|
|
else
|
|
pcurr_state->ts1 = true;
|
|
|
|
mutex_unlock(&tsc_device->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_enable_disable_tsif() - Enable/disable a TSIF.
|
|
*
|
|
* @tsc_mux: TSC Mux device.
|
|
* @source: The TSIF to enable or disable.
|
|
* @operation: The operation to perform: 0- enable, 1- disable.
|
|
*
|
|
* Enable or disable the specified TSIF, which consequently will block the TS
|
|
* flowing through this TSIF. The update is done by modifying a HW register.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_enable_disable_tsif(struct tsc_mux_chdev *tsc_mux,
|
|
enum tsc_source source, int operation)
|
|
{
|
|
int ret = 0;
|
|
u32 reg;
|
|
u32 addr_offs;
|
|
int reg_offs;
|
|
int curr_disable_state;
|
|
|
|
switch (source) {
|
|
case TSC_SOURCE_EXTERNAL0:
|
|
reg_offs = 0;
|
|
addr_offs = TSC_IN_IFC_EXT;
|
|
break;
|
|
case TSC_SOURCE_EXTERNAL1:
|
|
reg_offs = 16;
|
|
addr_offs = TSC_IN_IFC_EXT;
|
|
break;
|
|
case TSC_SOURCE_INTERNAL:
|
|
reg_offs = 0;
|
|
addr_offs = TSC_IN_IFC_CFG_INT;
|
|
break;
|
|
case TSC_SOURCE_CICAM:
|
|
reg_offs = 16;
|
|
addr_offs = TSC_IN_IFC_CFG_INT;
|
|
break;
|
|
default:
|
|
pr_err("%s: unidentified source parameter\n", __func__);
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
/* Reading the current enable/disable state from the register */
|
|
reg = readl_relaxed(tsc_device->base + addr_offs);
|
|
curr_disable_state = GETL_BITS(reg, TSIF_DISABLE_OFFS + reg_offs,
|
|
TSIF_DISABLE_OFFS + reg_offs);
|
|
/* If the current state equals the new state- return success */
|
|
if (curr_disable_state == operation)
|
|
return ret;
|
|
|
|
if (operation == TSIF_INPUT_DISABLE) {
|
|
if (source == TSC_SOURCE_EXTERNAL0 ||
|
|
source == TSC_SOURCE_EXTERNAL1) {
|
|
/* Disabling the TS-in pins in the TLMM */
|
|
ret = tsc_suspend_ts_pins(source);
|
|
if (ret != 0) {
|
|
pr_err("%s: Error suspending TS-in pins",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
}
|
|
SET_BIT((reg_offs + TSIF_DISABLE_OFFS), reg);
|
|
} else {
|
|
if (source == TSC_SOURCE_EXTERNAL0 ||
|
|
source == TSC_SOURCE_EXTERNAL1) {
|
|
/* Enabling the TS-in pins in the TLMM */
|
|
ret = tsc_activate_ts_pins(source);
|
|
if (ret != 0) {
|
|
pr_err("%s: Error activating TS-in pins",
|
|
__func__);
|
|
return ret;
|
|
}
|
|
}
|
|
CLEAR_BIT((reg_offs + TSIF_DISABLE_OFFS), reg);
|
|
}
|
|
|
|
/* Writing back to the reg the enable/disable of the TSIF */
|
|
writel_relaxed(reg, tsc_device->base + addr_offs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_route_mux() - Configuring one of the TSC muxes.
|
|
*
|
|
* @tsc_mux: TSC Mux device.
|
|
* @source: The requested TS source to be selected by the mux.
|
|
* @dest: The requested mux.
|
|
*
|
|
* Configuring the specified mux to pass the TS indicated by the src parameter.
|
|
* The update is done by modifying a HW register.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_route_mux(struct tsc_mux_chdev *tsc_mux, enum tsc_source source,
|
|
enum tsc_dest dest)
|
|
{
|
|
int ret = 0;
|
|
u32 mux_cfg_reg;
|
|
int src_val;
|
|
|
|
switch (source) {
|
|
case TSC_SOURCE_EXTERNAL0:
|
|
src_val = MUX_EXTERNAL_DEMOD_0;
|
|
break;
|
|
case TSC_SOURCE_EXTERNAL1:
|
|
src_val = MUX_EXTERNAL_DEMOD_1;
|
|
break;
|
|
case TSC_SOURCE_INTERNAL:
|
|
src_val = MUX_INTERNAL_DEMOD;
|
|
break;
|
|
case TSC_SOURCE_CICAM:
|
|
src_val = MUX_CICAM;
|
|
break;
|
|
default:
|
|
pr_err("%s: unidentified source parameter\n", __func__);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
/* Reading the current muxes state, to change only the requested mux */
|
|
mux_cfg_reg = readl_relaxed(tsc_device->base + TSC_MUX_CFG);
|
|
|
|
switch (dest) {
|
|
case TSC_DEST_TSPP0:
|
|
mux_cfg_reg &= ~(0x3 << MUX0_OFFS);
|
|
mux_cfg_reg |= (src_val << MUX0_OFFS);
|
|
break;
|
|
case TSC_DEST_TSPP1:
|
|
mux_cfg_reg &= ~(0x3 << MUX1_OFFS);
|
|
mux_cfg_reg |= (src_val << MUX1_OFFS);
|
|
break;
|
|
case TSC_DEST_CICAM:
|
|
if (src_val == TSC_SOURCE_CICAM) {
|
|
pr_err("%s: Error: CICAM cannot be source and dest\n",
|
|
__func__);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
mux_cfg_reg &= ~(0x3 << MUX_CAM_OFFS);
|
|
mux_cfg_reg |= (src_val << MUX_CAM_OFFS);
|
|
break;
|
|
default:
|
|
pr_err("%s: unidentified dest parameter\n", __func__);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
writel_relaxed(mux_cfg_reg, tsc_device->base + TSC_MUX_CFG);
|
|
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* is_tsc_idle() - Checking if TSC is idle.
|
|
*
|
|
* @tsc_ci: TSC CI device.
|
|
*
|
|
* Reading the TSC state-machine register and checking if the TSC is busy in
|
|
* one of the operations reflected by this register.
|
|
*
|
|
* Return true if the TSC is idle and false if it's busy.
|
|
*/
|
|
static bool is_tsc_idle(struct tsc_ci_chdev *tsc_ci)
|
|
{
|
|
u32 fsm_reg;
|
|
|
|
fsm_reg = readl_relaxed(tsc_device->base + TSC_FSM_STATE);
|
|
if (GETL_BITS(fsm_reg, FSM_STATE_BUFFER_BEG, FSM_STATE_BUFFER_END) ||
|
|
GETL_BITS(fsm_reg, FSM_STATE_POLL_BEG, FSM_STATE_POLL_END) ||
|
|
GETL_BITS(fsm_reg, FSM_STATE_BYTE_BEG, FSM_STATE_BYTE_END) ||
|
|
GETL_BITS(fsm_reg, FSM_STATE_MEM_WR_BEG,
|
|
FSM_STATE_MEM_WR_END) ||
|
|
GETL_BITS(fsm_reg, FSM_STATE_MEM_RD_BEG,
|
|
FSM_STATE_MEM_RD_END) ||
|
|
GETL_BITS(fsm_reg, FSM_STATE_IO_RD_BEG, FSM_STATE_IO_RD_END) ||
|
|
GETL_BITS(fsm_reg, FSM_STATE_IO_WR_BEG, FSM_STATE_IO_WR_END) ||
|
|
tsc_ci->data_busy)
|
|
return false;
|
|
|
|
tsc_ci->data_busy = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* tsc_power_on_buff_mode_clocks() - power-on the TSPP2 and VBIF clocks.
|
|
*
|
|
* Power-on the TSPP2 and the VBIF clocks required for buffer mode transaction.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_power_on_buff_mode_clocks(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = clk_prepare_enable(tsc_device->tspp2_core_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start tspp2_core_clk", __func__);
|
|
goto err_tspp2;
|
|
}
|
|
ret = clk_prepare_enable(tsc_device->vbif_tspp2_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start vbif_tspp2_clk", __func__);
|
|
goto err_vbif_tspp2;
|
|
}
|
|
ret = clk_prepare_enable(tsc_device->vbif_ahb_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start vbif_ahb_clk", __func__);
|
|
goto err_vbif_ahb;
|
|
}
|
|
ret = clk_prepare_enable(tsc_device->vbif_axi_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start vbif_axi_clk", __func__);
|
|
goto err_vbif_axi;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_vbif_axi:
|
|
clk_disable_unprepare(tsc_device->vbif_ahb_clk);
|
|
err_vbif_ahb:
|
|
clk_disable_unprepare(tsc_device->vbif_tspp2_clk);
|
|
err_vbif_tspp2:
|
|
clk_disable_unprepare(tsc_device->tspp2_core_clk);
|
|
err_tspp2:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_power_off_buff_mode_clocks() - power-off the SPP2 and VBIF clocks.
|
|
*
|
|
* Power-off the TSPP2 and the VBIF clocks required for buffer mode transaction.
|
|
*/
|
|
static void tsc_power_off_buff_mode_clocks(void)
|
|
{
|
|
clk_disable_unprepare(tsc_device->vbif_axi_clk);
|
|
clk_disable_unprepare(tsc_device->vbif_ahb_clk);
|
|
clk_disable_unprepare(tsc_device->tspp2_core_clk);
|
|
clk_disable_unprepare(tsc_device->vbif_tspp2_clk);
|
|
}
|
|
|
|
/**
|
|
* tsc_config_cam_data_transaction() - Configuring a new data transaction.
|
|
*
|
|
* @addr_size: The value for the address_size register field- address when
|
|
* using single byte-mode, and size when using buffer mode.
|
|
* @wr_data: the value for the wr_data register field- data to write to the
|
|
* cam when using single byte mode.
|
|
* @io_mem: The value for the io_mem register field- 1 for IO transaction,
|
|
* 0 for memory transaction.
|
|
* @read_write: The value for the read_write register field- 1 for read
|
|
* transaction, 0 for write transaction.
|
|
* @buff_mode: The value for the buff_mode register field- 1 for buffer mode,
|
|
* 0 for single byte mode.
|
|
*
|
|
* Configuring the cam cmd register with the specified parameters, to initiate
|
|
* data transaction with the cam.
|
|
*/
|
|
static void tsc_config_cam_data_transaction(u16 addr_size,
|
|
u8 wr_data,
|
|
uint io_mem,
|
|
uint read_write,
|
|
uint buff_mode)
|
|
{
|
|
u32 cam_cmd_reg = 0;
|
|
|
|
cam_cmd_reg |= (addr_size << CAM_CMD_ADDR_SIZE_OFFS);
|
|
cam_cmd_reg |= (wr_data << CAM_CMD_WR_DATA_OFFS);
|
|
cam_cmd_reg |= (io_mem << CAM_CMD_IO_MEM_OFFS);
|
|
cam_cmd_reg |= (read_write << CAM_CMD_RD_WR_OFFS);
|
|
cam_cmd_reg |= (buff_mode << CAM_CMD_BUFF_MODE_OFFS);
|
|
writel_relaxed(cam_cmd_reg, tsc_device->base + TSC_CAM_CMD);
|
|
}
|
|
|
|
/**
|
|
* tsc_data_transaction() - Blocking function that manage the data transactions.
|
|
*
|
|
* @tsc_ci: TSC CI device.
|
|
* @io_mem: The value for the io_mem register field- 1 for IO transaction,
|
|
* 0 for memory transaction.
|
|
* @read_write: The value for the read_write register field- 1 for read
|
|
* transaction, 0 for write transaction.
|
|
* @buff_mode: The value for the buff_mode register field- 1 for buffer mode,
|
|
* 0 for single byte mode.
|
|
* @arg: The argument received from the user-space via a data transaction
|
|
* IOCTL. It is from one of the two following types:
|
|
* "struct tsc_single_byte_mode" and "struct tsc_buffer_mode".
|
|
*
|
|
* Receiving the transaction paramters from the user-space. Configure the HW
|
|
* registers to initiate a data transaction with the cam. Wait for an
|
|
* interrupt indicating the transaction is over and return the the data read
|
|
* from the cam in case of single-byte read transaction.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_data_transaction(struct tsc_ci_chdev *tsc_ci, uint io_mem,
|
|
uint read_write, uint buff_mode, unsigned long arg)
|
|
{
|
|
struct tsc_single_byte_mode arg_byte;
|
|
struct tsc_buffer_mode arg_buff;
|
|
u16 addr_size;
|
|
u8 wr_data;
|
|
uint timeout;
|
|
u32 cam_cmd_reg;
|
|
struct ion_handle *ion_handle = NULL;
|
|
ion_phys_addr_t iova = 0;
|
|
unsigned long buffer_size = 0;
|
|
unsigned long flags = 0;
|
|
int ret = 0;
|
|
|
|
if (!arg)
|
|
return -EINVAL;
|
|
|
|
/* make sure the tsc is in idle state before configuring the cam */
|
|
if (!is_tsc_idle(tsc_ci)) {
|
|
ret = -EBUSY;
|
|
goto finish;
|
|
}
|
|
|
|
INIT_COMPLETION(tsc_ci->transaction_finish);
|
|
|
|
/* copying data from the ioctl parameter */
|
|
if (buff_mode == SINGLE_BYTE_MODE) {
|
|
if (copy_from_user(&arg_byte, (void *)arg,
|
|
sizeof(struct tsc_single_byte_mode))) {
|
|
ret = -EFAULT;
|
|
goto err_copy_arg;
|
|
}
|
|
addr_size = arg_byte.address;
|
|
if (IO_TRANSACTION == io_mem &&
|
|
addr_size > CICAM_MAX_ADDRESS) {
|
|
pr_err("%s: wrong address parameter: %d\n", __func__,
|
|
addr_size);
|
|
ret = -EFAULT;
|
|
goto err_copy_arg;
|
|
}
|
|
wr_data = arg_byte.data;
|
|
timeout = arg_byte.timeout;
|
|
} else {
|
|
if (copy_from_user(&arg_buff, (void *)arg,
|
|
sizeof(struct tsc_buffer_mode))) {
|
|
ret = -EFAULT;
|
|
goto err_copy_arg;
|
|
}
|
|
addr_size = arg_buff.buffer_size;
|
|
if (!addr_size) {
|
|
pr_err("%s: size parameter is 0\n", __func__);
|
|
ret = -EFAULT;
|
|
goto err_copy_arg;
|
|
}
|
|
wr_data = 0;
|
|
timeout = arg_buff.timeout;
|
|
|
|
/* import ion handle from the ion fd passed from user-space */
|
|
ion_handle = ion_import_dma_buf
|
|
(tsc_device->iommu_info.ion_client, arg_buff.buffer_fd);
|
|
if (IS_ERR_OR_NULL(ion_handle)) {
|
|
pr_err("%s: get_ION_handle failed\n", __func__);
|
|
ret = -EIO;
|
|
goto err_ion_handle;
|
|
}
|
|
|
|
/*
|
|
* mapping the ion handle to the VBIF and get the virtual
|
|
* address
|
|
*/
|
|
ret = ion_map_iommu(tsc_device->iommu_info.ion_client,
|
|
ion_handle, tsc_device->iommu_info.domain_num,
|
|
tsc_device->iommu_info.partition_num, SZ_4K,
|
|
0, &iova, &buffer_size, 0, 0);
|
|
|
|
if (ret != 0) {
|
|
pr_err("%s: get_ION_kernel physical addr fail\n",
|
|
__func__);
|
|
goto err_ion_map;
|
|
}
|
|
|
|
/*
|
|
* writing the buffer virtual address to the register for buffer
|
|
* address of buffer mode
|
|
*/
|
|
if (read_write == READ_TRANSACTION)
|
|
writel_relaxed(iova,
|
|
tsc_device->base + TSC_RD_BUFF_ADDR);
|
|
else /* write transaction */
|
|
writel_relaxed(iova,
|
|
tsc_device->base + TSC_WR_BUFF_ADDR);
|
|
}
|
|
|
|
/* configuring the cam command register */
|
|
tsc_config_cam_data_transaction(addr_size, wr_data, io_mem, read_write,
|
|
buff_mode);
|
|
|
|
/*
|
|
* This function assume the mutex is locked before calling the function,
|
|
* so mutex has to be unlocked before going to sleep when waiting for
|
|
* the transaction.
|
|
*/
|
|
mutex_unlock(&tsc_ci->mutex);
|
|
/* waiting for EOT interrupt or timeout */
|
|
if (!wait_for_completion_timeout(&tsc_ci->transaction_complete,
|
|
msecs_to_jiffies(timeout))) {
|
|
pr_err("%s: Error: wait for transaction timed-out\n", __func__);
|
|
ret = -ETIMEDOUT;
|
|
mutex_lock(&tsc_ci->mutex);
|
|
/* Aborting the transaction if it's buffer mode */
|
|
if (buff_mode) {
|
|
cam_cmd_reg = readl_relaxed(tsc_device->base +
|
|
TSC_CAM_CMD);
|
|
SET_BIT(CAM_CMD_ABORT, cam_cmd_reg);
|
|
writel_relaxed(cam_cmd_reg, tsc_device->base +
|
|
TSC_CAM_CMD);
|
|
}
|
|
goto finish;
|
|
}
|
|
mutex_lock(&tsc_ci->mutex);
|
|
|
|
/* Checking if transaction ended with error */
|
|
spin_lock_irqsave(&tsc_ci->spinlock, flags);
|
|
if (tsc_ci->transaction_state == TRANSACTION_ERROR) {
|
|
tsc_ci->transaction_state = BEFORE_TRANSACTION;
|
|
spin_unlock_irqrestore(&tsc_ci->spinlock, flags);
|
|
pr_err("%s: Transaction error\n", __func__);
|
|
ret = -EBADE; /* Invalid exchange error code */
|
|
goto finish;
|
|
} else if (tsc_ci->transaction_state == TRANSACTION_CARD_REMOVED) {
|
|
tsc_ci->transaction_state = BEFORE_TRANSACTION;
|
|
spin_unlock_irqrestore(&tsc_ci->spinlock, flags);
|
|
pr_err("%s: Card was removed during the transaction. Aborting\n",
|
|
__func__);
|
|
ret = -ECONNABORTED;
|
|
/* Aborting the transaction if it's buffer mode */
|
|
if (buff_mode) {
|
|
cam_cmd_reg = readl_relaxed(tsc_device->base +
|
|
TSC_CAM_CMD);
|
|
SET_BIT(CAM_CMD_ABORT, cam_cmd_reg);
|
|
writel_relaxed(cam_cmd_reg, tsc_device->base +
|
|
TSC_CAM_CMD);
|
|
}
|
|
goto finish;
|
|
}
|
|
|
|
/* reseting the argument after reading the interrupt type */
|
|
tsc_ci->transaction_state = BEFORE_TRANSACTION;
|
|
spin_unlock_irqrestore(&tsc_ci->spinlock, flags);
|
|
|
|
/*
|
|
* Only on case of read single byte operation, we need to copy the data
|
|
* to the arg data field
|
|
*/
|
|
if (buff_mode == SINGLE_BYTE_MODE && read_write == READ_TRANSACTION)
|
|
ret = put_user(readl_relaxed(tsc_device->base +
|
|
TSC_CAM_RD_DATA),
|
|
&((struct tsc_single_byte_mode *)arg)->data);
|
|
|
|
finish:
|
|
if (iova != 0)
|
|
ion_unmap_iommu(tsc_device->iommu_info.ion_client, ion_handle,
|
|
tsc_device->iommu_info.domain_num,
|
|
tsc_device->iommu_info.partition_num);
|
|
err_ion_map:
|
|
if (!IS_ERR_OR_NULL(ion_handle))
|
|
ion_free(tsc_device->iommu_info.ion_client, ion_handle);
|
|
err_ion_handle:
|
|
err_copy_arg:
|
|
tsc_ci->data_busy = false;
|
|
INIT_COMPLETION(tsc_ci->transaction_complete);
|
|
complete_all(&tsc_ci->transaction_finish);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_personality_change() - change the PCMCIA pins state.
|
|
*
|
|
* @pcmcia_state: The new state of the PCMCIA pins.
|
|
*
|
|
* Configure the TLMM pins of the PCMCIA according to received state and
|
|
* the current pinctrl configuration of the other pins. This function assums the
|
|
* PCMCIA pinctrl definitions were successfully parsed from the devicetree (this
|
|
* check is done at open device).
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_personality_change(enum tsc_cam_personality pcmcia_state)
|
|
{
|
|
int ret = 0;
|
|
struct pinctrl_info *ppinctrl = &tsc_device->pinctrl_info;
|
|
struct pinctrl_current_state *pcurr_state = &ppinctrl->curr_state;
|
|
u32 reg = 0;
|
|
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (pcmcia_state == (enum tsc_cam_personality)pcurr_state->pcmcia_state)
|
|
goto exit;
|
|
|
|
/* Transition from current pinctrl state to curr + new pcmcia state */
|
|
switch (pcmcia_state) {
|
|
case TSC_CICAM_PERSONALITY_CI:
|
|
if (pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_ci_card);
|
|
else if (pcurr_state->ts0 && !pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_ci_card);
|
|
else if (!pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_ci_card);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ci_card);
|
|
break;
|
|
case TSC_CICAM_PERSONALITY_CIPLUS:
|
|
if (pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts_ci_plus);
|
|
else if (pcurr_state->ts0 && !pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0_ci_plus);
|
|
else if (!pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1_ci_plus);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ci_plus);
|
|
break;
|
|
case TSC_CICAM_PERSONALITY_DISABLE:
|
|
if (pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->dual_ts);
|
|
else if (pcurr_state->ts0 && !pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts0);
|
|
else if (!pcurr_state->ts0 && pcurr_state->ts1)
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->ts1);
|
|
else
|
|
ret = pinctrl_select_state(ppinctrl->pinctrl,
|
|
ppinctrl->disable);
|
|
break;
|
|
default:
|
|
pr_err("%s: Wrong personality parameter\n", __func__);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (ret != 0) {
|
|
pr_err("%s: error changing PCMCIA pins. ret value = %d\n",
|
|
__func__, ret);
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
/* Update the current pcmcia state in the internal struct */
|
|
pcurr_state->pcmcia_state = (enum pcmcia_state)pcmcia_state;
|
|
|
|
/*
|
|
* Setting CAM TSIF OE to enable I/O transactions for CI/+ cards
|
|
* or clearing it when moving to disable state
|
|
*/
|
|
if (TSC_CICAM_PERSONALITY_CI == pcmcia_state ||
|
|
TSC_CICAM_PERSONALITY_CIPLUS == pcmcia_state) {
|
|
SET_BIT(TSC_CICAM_TSIF_OE_OFFS, reg);
|
|
writel_relaxed(reg, tsc_device->base + TSC_CICAM_TSIF);
|
|
} else {
|
|
CLEAR_BIT(TSC_CICAM_TSIF_OE_OFFS, reg);
|
|
writel_relaxed(reg, tsc_device->base + TSC_CICAM_TSIF);
|
|
}
|
|
|
|
exit:
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_reset_cam() - HW reset to the CAM.
|
|
*
|
|
* Toggle the reset pin of the pcmcia to make a HW reset.
|
|
* This function assumes that pinctrl_select_state was already called on the
|
|
* reset pin with its active state (happens during personality change).
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_reset_cam(void)
|
|
{
|
|
int ret;
|
|
int reset_gpio = tsc_device->reset_cam_gpio;
|
|
|
|
/* Toggle the GPIO to create a reset pulse */
|
|
ret = gpio_direction_output(reset_gpio, 0); /* Make sure it's 0 */
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
ret = gpio_direction_output(reset_gpio, 1); /* Assert */
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
/*
|
|
* Waiting to enable the CAM to process the assertion before the
|
|
* deassertion. 1ms is needed for this processing.
|
|
*/
|
|
usleep(1000);
|
|
|
|
ret = gpio_direction_output(reset_gpio, 0); /* Deassert */
|
|
if (ret != 0)
|
|
goto err;
|
|
|
|
return 0;
|
|
err:
|
|
pr_err("%s: Failed writing to reset cam GPIO\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_reset_registers() - Reset the TSC registers.
|
|
*
|
|
* Write specific reset values to the TSC registers, managed by the driver.
|
|
*/
|
|
static void tsc_reset_registers(void)
|
|
{
|
|
/* Reset state - all mux transfer ext. demod 0 */
|
|
writel_relaxed(0x00000000, tsc_device->base + TSC_MUX_CFG);
|
|
|
|
/* Disabling TSIFs inputs, putting polarity to normal, data as serial */
|
|
writel_relaxed(0x02000200, tsc_device->base + TSC_IN_IFC_EXT);
|
|
writel_relaxed(0x02000200, tsc_device->base + TSC_IN_IFC_CFG_INT);
|
|
|
|
/* Reseting TSC_FSM_STATE_MASK to represent all the states but poll */
|
|
writel_relaxed(0x3333300F, tsc_device->base + TSC_FSM_STATE_MASK);
|
|
|
|
/* Clearing all the CAM interrupt */
|
|
writel_relaxed(0x1F, tsc_device->base + TSC_IRQ_CLR);
|
|
|
|
/* Disabling all cam interrupts (enable is done at - open) */
|
|
writel_relaxed(0x00, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
/* Disabling HW polling */
|
|
writel_relaxed(0x00, tsc_device->base + TSC_CIP_CFG);
|
|
|
|
/* Reset state - address for read/write buffer */
|
|
writel_relaxed(0x00000000, tsc_device->base + TSC_RD_BUFF_ADDR);
|
|
writel_relaxed(0x00000000, tsc_device->base + TSC_WR_BUFF_ADDR);
|
|
|
|
/* Clearing false cd counter */
|
|
writel_relaxed(0x01, tsc_device->base + TSC_FALSE_CD_CLR);
|
|
writel_relaxed(0x00, tsc_device->base + TSC_FALSE_CD_CLR);
|
|
|
|
/* Disabling TSIF out to cicam and IO read/write with the CAM */
|
|
writel_relaxed(0x00000000, tsc_device->base + TSC_CICAM_TSIF);
|
|
}
|
|
|
|
/**
|
|
* tsc_disable_tsifs() - Disable all the TSC Tsifs.
|
|
*
|
|
* Disable the TSIFs of the ext. demods, the int. demod and the cam on both
|
|
* directions.
|
|
*/
|
|
static void tsc_disable_tsifs(void)
|
|
{
|
|
u32 reg;
|
|
|
|
/* Ext. TSIFs */
|
|
reg = readl_relaxed(tsc_device->base + TSC_IN_IFC_EXT);
|
|
SET_BIT(TSIF_DISABLE_OFFS, reg);
|
|
SET_BIT((TSIF_DISABLE_OFFS + 16), reg);
|
|
writel_relaxed(reg, tsc_device->base + TSC_IN_IFC_EXT);
|
|
|
|
/* Int. TSIF and TSIF-in from the CAM */
|
|
reg = readl_relaxed(tsc_device->base + TSC_IN_IFC_CFG_INT);
|
|
SET_BIT(TSIF_DISABLE_OFFS, reg);
|
|
SET_BIT((TSIF_DISABLE_OFFS + 16), reg);
|
|
writel_relaxed(reg, tsc_device->base + TSC_IN_IFC_CFG_INT);
|
|
}
|
|
|
|
/**
|
|
* tsc_power_on_clocks() - power-on the TSC clocks.
|
|
*
|
|
* Power-on the TSC clocks required for Mux and/or CI operations.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_power_on_clocks(void)
|
|
{
|
|
int ret = 0;
|
|
unsigned long rate_in_hz = 0;
|
|
|
|
/* Enabling the clocks */
|
|
ret = clk_prepare_enable(tsc_device->ahb_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start tsc_ahb_clk", __func__);
|
|
return ret;
|
|
}
|
|
|
|
/* We need to set the rate of ci clock before enabling it */
|
|
rate_in_hz = clk_round_rate(tsc_device->ci_clk, 1);
|
|
if (clk_set_rate(tsc_device->ci_clk, rate_in_hz)) {
|
|
pr_err("%s: Failed to set rate to tsc_ci clock\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
ret = clk_prepare_enable(tsc_device->ci_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start tsc_ci_clk", __func__);
|
|
goto err;
|
|
}
|
|
|
|
return ret;
|
|
err:
|
|
clk_disable_unprepare(tsc_device->ahb_clk);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_power_off_clocks() - power-off the TSC clocks.
|
|
*
|
|
* Power-off the TSC clocks required for Mux and/or CI operations.
|
|
*/
|
|
static void tsc_power_off_clocks(void)
|
|
{
|
|
clk_disable_unprepare(tsc_device->ahb_clk);
|
|
clk_disable_unprepare(tsc_device->ci_clk);
|
|
}
|
|
|
|
/**
|
|
* tsc_mux_power_on_clocks() - power-on the TSC Mux clocks.
|
|
*
|
|
* Power-on the TSC clocks required only for Mux operations, and not for CI.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_mux_power_on_clocks(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Setting the cicam clock rate */
|
|
ret = clk_set_rate(tsc_device->cicam_ts_clk, CICAM_CLK_RATE_7MHZ);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't set rate for tsc_cicam_ts_clk", __func__);
|
|
goto err_set_rate;
|
|
}
|
|
|
|
/* Setting the TSC serial clock rate */
|
|
ret = clk_set_rate(tsc_device->ser_clk, TSC_SER_CLK_RATE);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't set rate for tsc serial clock", __func__);
|
|
goto err_set_rate;
|
|
}
|
|
|
|
/* Setting the TSC parallel clock rate */
|
|
ret = clk_set_rate(tsc_device->par_clk, TSC_PAR_CLK_RATE);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't set rate for tsc parallel clock", __func__);
|
|
goto err_set_rate;
|
|
}
|
|
|
|
/* Enabling the clocks */
|
|
ret = clk_prepare_enable(tsc_device->ser_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start tsc_ser_clk", __func__);
|
|
goto err_ser_clk;
|
|
}
|
|
ret = clk_prepare_enable(tsc_device->par_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start tsc_par_clk", __func__);
|
|
goto err_par_clk;
|
|
}
|
|
ret = clk_prepare_enable(tsc_device->cicam_ts_clk);
|
|
if (ret != 0) {
|
|
pr_err("%s: Can't start tsc_cicam_ts_clk", __func__);
|
|
goto err_cicam_ts_clk;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_cicam_ts_clk:
|
|
clk_disable_unprepare(tsc_device->par_clk);
|
|
err_par_clk:
|
|
clk_disable_unprepare(tsc_device->ser_clk);
|
|
err_ser_clk:
|
|
err_set_rate:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_mux_power_off_clocks() - power-off the TSC Mux clocks.
|
|
*
|
|
* Power-off the TSC clocks required only for Mux operations, and not for CI.
|
|
*/
|
|
static void tsc_mux_power_off_clocks(void)
|
|
{
|
|
clk_disable_unprepare(tsc_device->ser_clk);
|
|
clk_disable_unprepare(tsc_device->par_clk);
|
|
clk_disable_unprepare(tsc_device->cicam_ts_clk);
|
|
}
|
|
|
|
/**
|
|
* tsc_device_power_up() - Power init done by the first device opened.
|
|
*
|
|
* Check if it's the first device and enable the GDSC,power-on the TSC clocks
|
|
* required for both Mux and CI, Vote for the bus and reset the registers to a
|
|
* known default values.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_device_power_up(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (mutex_lock_interruptible(&tsc_device->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (tsc_device->num_device_open > 0)
|
|
goto not_first_device;
|
|
|
|
/* Enable the GDSC */
|
|
ret = regulator_enable(tsc_device->gdsc);
|
|
if (ret != 0) {
|
|
pr_err("%s: Failed to enable regulator\n", __func__);
|
|
goto err_regulator;
|
|
}
|
|
|
|
/* Power-on the clocks needed by Mux and CI */
|
|
ret = tsc_power_on_clocks();
|
|
if (ret != 0)
|
|
goto err_power_clocks;
|
|
|
|
/* Voting for bus bandwidth */
|
|
if (tsc_device->bus_client) {
|
|
ret = msm_bus_scale_client_update_request
|
|
(tsc_device->bus_client, 1);
|
|
if (ret) {
|
|
pr_err("%s: Can't enable bus\n", __func__);
|
|
goto err_bus;
|
|
}
|
|
}
|
|
|
|
/* Reset the TSC TLMM pins to a default state */
|
|
ret = pinctrl_select_state(tsc_device->pinctrl_info.pinctrl,
|
|
tsc_device->pinctrl_info.disable);
|
|
if (ret != 0) {
|
|
pr_err("%s: Failed to disable the TLMM pins\n", __func__);
|
|
goto err_pinctrl;
|
|
}
|
|
/* Update the current pinctrl state in the internal struct */
|
|
tsc_device->pinctrl_info.curr_state.ts0 = false;
|
|
tsc_device->pinctrl_info.curr_state.ts1 = false;
|
|
tsc_device->pinctrl_info.curr_state.pcmcia_state =
|
|
TSC_CICAM_PERSONALITY_DISABLE;
|
|
|
|
/* Reset TSC registers to a default known state */
|
|
tsc_reset_registers();
|
|
|
|
not_first_device:
|
|
tsc_device->num_device_open++;
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return ret;
|
|
|
|
err_pinctrl:
|
|
if (tsc_device->bus_client)
|
|
msm_bus_scale_client_update_request(tsc_device->bus_client, 0);
|
|
err_bus:
|
|
tsc_power_off_clocks();
|
|
err_power_clocks:
|
|
regulator_disable(tsc_device->gdsc);
|
|
err_regulator:
|
|
mutex_unlock(&tsc_device->mutex);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_device_power_off() - Power off done by the last device closed.
|
|
*
|
|
* Check if it's the last device and unvote the bus, power-off the TSC clocks
|
|
* required for both Mux and CI, disable the TLMM pins and disable the GDSC.
|
|
*/
|
|
static void tsc_device_power_off(void)
|
|
{
|
|
mutex_lock(&tsc_device->mutex);
|
|
|
|
if (tsc_device->num_device_open > 1)
|
|
goto not_last_device;
|
|
|
|
pinctrl_select_state(tsc_device->pinctrl_info.pinctrl,
|
|
tsc_device->pinctrl_info.disable);
|
|
if (tsc_device->bus_client)
|
|
msm_bus_scale_client_update_request(tsc_device->bus_client, 0);
|
|
|
|
tsc_power_off_clocks();
|
|
regulator_disable(tsc_device->gdsc);
|
|
|
|
not_last_device:
|
|
tsc_device->num_device_open--;
|
|
mutex_unlock(&tsc_device->mutex);
|
|
}
|
|
|
|
|
|
/************************** TSC file operations **************************/
|
|
/**
|
|
* tsc_mux_open() - init the TSC Mux char device.
|
|
*
|
|
* @inode: The inode associated with the TSC Mux device.
|
|
* @flip: The file pointer associated with the TSC Mux device.
|
|
*
|
|
* Enables only one open Mux device.
|
|
* Init all the data structures and vote for all the power resources needed.
|
|
* Manage reference counters for initiating resources upon first open.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_mux_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct tsc_mux_chdev *tsc_mux;
|
|
int ret = 0;
|
|
u32 ena_reg;
|
|
|
|
if (mutex_lock_interruptible(&tsc_device->mux_chdev.mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (tsc_device->num_mux_opened > 0) {
|
|
pr_err("%s: Too many devices open\n", __func__);
|
|
mutex_unlock(&tsc_device->mux_chdev.mutex);
|
|
return -EMFILE;
|
|
}
|
|
tsc_device->num_mux_opened++;
|
|
|
|
tsc_mux = container_of(inode->i_cdev, struct tsc_mux_chdev, cdev);
|
|
filp->private_data = tsc_mux;
|
|
|
|
/* Init all resources if it's the first device (checked inside) */
|
|
ret = tsc_device_power_up();
|
|
if (ret != 0)
|
|
goto err_first_device;
|
|
|
|
/* Power-on the Mux clocks */
|
|
ret = tsc_mux_power_on_clocks();
|
|
if (ret != 0)
|
|
goto err_mux_clocks;
|
|
|
|
/* Init TSC Mux args */
|
|
spin_lock_init(&tsc_mux->spinlock);
|
|
init_waitqueue_head(&tsc_mux->poll_queue);
|
|
tsc_mux->rate_interrupt = false;
|
|
|
|
/* Enabling TSC Mux cam interrupt of rate mismatch */
|
|
ena_reg = readl_relaxed(tsc_device->base + TSC_IRQ_ENA);
|
|
SET_BIT(CAM_IRQ_RATE_MISMATCH_OFFS, ena_reg);
|
|
writel_relaxed(ena_reg, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
mutex_unlock(&tsc_device->mux_chdev.mutex);
|
|
|
|
return ret;
|
|
|
|
err_mux_clocks:
|
|
/* De-init all resources if it's the only device (checked inside) */
|
|
tsc_device_power_off();
|
|
err_first_device:
|
|
tsc_device->num_mux_opened--;
|
|
mutex_unlock(&tsc_device->mux_chdev.mutex);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_ci_open() - init the TSC CI char device.
|
|
*
|
|
* @inode: The inode associated with the TSC Mux device.
|
|
* @flip: The file pointer associated with the TSC Mux device.
|
|
*
|
|
* Enables only one open CI device.
|
|
* Init all the data structures and vote for all the power resources needed.
|
|
* Manage reference counters for initiating resources upon first open.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_ci_open(struct inode *inode, struct file *filp)
|
|
{
|
|
struct tsc_ci_chdev *tsc_ci;
|
|
int ret = 0;
|
|
u32 ena_reg;
|
|
|
|
if (mutex_lock_interruptible(&tsc_device->ci_chdev.mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
if (tsc_device->num_ci_opened > 0) {
|
|
pr_err("%s: Too many devices open\n", __func__);
|
|
mutex_unlock(&tsc_device->ci_chdev.mutex);
|
|
return -EMFILE;
|
|
}
|
|
|
|
if (!tsc_device->pinctrl_info.is_pcmcia) {
|
|
pr_err("%s: No pcmcia pinctrl definitions were found in the TSC devicetree\n",
|
|
__func__);
|
|
mutex_unlock(&tsc_device->ci_chdev.mutex);
|
|
return -EPERM;
|
|
}
|
|
|
|
tsc_device->num_ci_opened++;
|
|
|
|
tsc_ci = container_of(inode->i_cdev, struct tsc_ci_chdev, cdev);
|
|
filp->private_data = tsc_ci;
|
|
|
|
/* Init all resources if it's the first device (checked inside) */
|
|
ret = tsc_device_power_up();
|
|
if (ret != 0)
|
|
goto err_first_device;
|
|
|
|
/* powering-up the tspp2 and VBIF clocks */
|
|
ret = tsc_power_on_buff_mode_clocks();
|
|
if (ret != 0)
|
|
goto err_buff_clocks;
|
|
|
|
/* Request reset CAM GPIO */
|
|
ret = gpio_request(tsc_device->reset_cam_gpio, "tsc_ci_reset");
|
|
if (ret != 0) {
|
|
pr_err("%s: Failed to request reset CAM GPIO\n", __func__);
|
|
goto err_gpio_req;
|
|
}
|
|
|
|
/* Set the reset line to default "no card" state */
|
|
ret = gpio_direction_output(tsc_device->reset_cam_gpio, 1);
|
|
if (ret != 0) {
|
|
pr_err("%s: Failed to assert the reset CAM GPIO\n", __func__);
|
|
goto err_assert;
|
|
}
|
|
|
|
/* Attach the iommu group to support the required memory mapping */
|
|
if (!tsc_iommu_bypass) {
|
|
ret = iommu_attach_group(tsc_device->iommu_info.domain,
|
|
tsc_device->iommu_info.group);
|
|
if (ret != 0) {
|
|
pr_err("%s: iommu_attach_group failed\n", __func__);
|
|
goto err_iommu_attach;
|
|
}
|
|
}
|
|
|
|
/* Init TSC CI args */
|
|
spin_lock_init(&tsc_ci->spinlock);
|
|
init_waitqueue_head(&tsc_ci->poll_queue);
|
|
tsc_ci->transaction_state = BEFORE_TRANSACTION;
|
|
tsc_ci->data_busy = false;
|
|
tsc_device->card_power = false;
|
|
|
|
/*
|
|
* Init hw card status flag according to the pins' state.
|
|
* No need to protect from interrupt because the handler is not
|
|
* registred yet.
|
|
*/
|
|
tsc_update_hw_card_status();
|
|
tsc_ci->card_status = tsc_device->hw_card_status;
|
|
|
|
/* If a card is already inserted - need to power up the card */
|
|
if (tsc_device->hw_card_status == TSC_CARD_STATUS_DETECTED) {
|
|
ret = tsc_card_power_up();
|
|
if (ret != 0)
|
|
pr_err("%s: card power-up failed\n", __func__);
|
|
else
|
|
tsc_device->card_power = true;
|
|
}
|
|
|
|
/* Enabling the TSC CI cam interrupts: EOT and Err */
|
|
ena_reg = readl_relaxed(tsc_device->base + TSC_IRQ_ENA);
|
|
SET_BIT(CAM_IRQ_EOT_OFFS, ena_reg);
|
|
SET_BIT(CAM_IRQ_ERR_OFFS, ena_reg);
|
|
writel_relaxed(ena_reg, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
/* Registering the CAM cmd interrupt handler */
|
|
ret = request_irq(tsc_device->cam_cmd_irq, tsc_cam_cmd_irq_handler,
|
|
IRQF_SHARED, dev_name(&tsc_device->pdev->dev),
|
|
tsc_device);
|
|
if (ret) {
|
|
pr_err("%s: failed to request TSC IRQ %d : %d",
|
|
__func__, tsc_device->cam_cmd_irq, ret);
|
|
goto err_cam_irq;
|
|
}
|
|
|
|
/*
|
|
* Registering the card detect interrupt handler (this interrupt is
|
|
* enabled by default, right after this registration)
|
|
*/
|
|
ret = request_threaded_irq(tsc_device->card_detection_irq,
|
|
NULL, tsc_card_detect_irq_thread_handler,
|
|
IRQF_ONESHOT | IRQF_TRIGGER_RISING,
|
|
dev_name(&tsc_device->pdev->dev), tsc_device);
|
|
if (ret) {
|
|
pr_err("%s: failed to request TSC IRQ %d : %d",
|
|
__func__, tsc_device->card_detection_irq, ret);
|
|
goto err_card_irq;
|
|
}
|
|
|
|
mutex_unlock(&tsc_device->ci_chdev.mutex);
|
|
|
|
return ret;
|
|
|
|
err_card_irq:
|
|
free_irq(tsc_device->cam_cmd_irq, tsc_device);
|
|
err_cam_irq:
|
|
if (!tsc_iommu_bypass)
|
|
iommu_detach_group(tsc_device->iommu_info.domain,
|
|
tsc_device->iommu_info.group);
|
|
err_iommu_attach:
|
|
gpio_free(tsc_device->reset_cam_gpio);
|
|
err_assert:
|
|
err_gpio_req:
|
|
tsc_power_off_buff_mode_clocks();
|
|
err_buff_clocks:
|
|
/* De-init all resources if it's the only device (checked inside) */
|
|
tsc_device_power_off();
|
|
err_first_device:
|
|
tsc_device->num_ci_opened--;
|
|
mutex_unlock(&tsc_device->ci_chdev.mutex);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_mux_release() - Release and close the TSC Mux char device.
|
|
*
|
|
* @inode: The inode associated with the TSC Mux device.
|
|
* @flip: The file pointer associated with the TSC Mux device.
|
|
*
|
|
* Release all the resources allocated for the Mux device and unvote power
|
|
* resources.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_mux_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct tsc_mux_chdev *tsc_mux;
|
|
u32 ena_reg;
|
|
|
|
tsc_mux = filp->private_data;
|
|
if (!tsc_mux)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&tsc_mux->mutex);
|
|
|
|
tsc_mux_power_off_clocks();
|
|
|
|
/* Disable the TSIFs */
|
|
tsc_disable_tsifs();
|
|
/* Disabling rate mismatch interrupt */
|
|
ena_reg = readl_relaxed(tsc_device->base + TSC_IRQ_ENA);
|
|
CLEAR_BIT(CAM_IRQ_RATE_MISMATCH_OFFS, ena_reg);
|
|
writel_relaxed(ena_reg, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
tsc_device_power_off();
|
|
|
|
tsc_device->num_mux_opened--;
|
|
mutex_unlock(&tsc_mux->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_ci_release() - Release and close the TSC CI char device.
|
|
*
|
|
* @inode: The inode associated with the TSC CI device.
|
|
* @flip: The file pointer associated with the TSC CI device.
|
|
*
|
|
* Release all the resources allocated for the CI device and unvote power
|
|
* resources.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_ci_release(struct inode *inode, struct file *filp)
|
|
{
|
|
struct tsc_ci_chdev *tsc_ci;
|
|
u32 ena_reg;
|
|
int ret;
|
|
|
|
tsc_ci = filp->private_data;
|
|
if (!tsc_ci)
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&tsc_ci->mutex);
|
|
|
|
/* If in the middle of a data transaction- wake-up completion */
|
|
if (tsc_ci->data_busy) {
|
|
/* Closing the device is similar in behavior to card removal */
|
|
tsc_ci->transaction_state = TRANSACTION_CARD_REMOVED;
|
|
mutex_unlock(&tsc_ci->mutex);
|
|
complete_all(&tsc_ci->transaction_complete);
|
|
wait_for_completion(&tsc_ci->transaction_finish);
|
|
mutex_lock(&tsc_ci->mutex);
|
|
}
|
|
|
|
/* clearing EOT and ERR interrupts */
|
|
ena_reg = readl_relaxed(tsc_device->base + TSC_IRQ_ENA);
|
|
CLEAR_BIT(CAM_IRQ_EOT_OFFS, ena_reg);
|
|
CLEAR_BIT(CAM_IRQ_ERR_OFFS, ena_reg);
|
|
writel_relaxed(ena_reg, tsc_device->base + TSC_IRQ_ENA);
|
|
|
|
/* Cancel the interrupt handlers registration */
|
|
free_irq(tsc_device->card_detection_irq, tsc_device);
|
|
free_irq(tsc_device->cam_cmd_irq, tsc_device);
|
|
|
|
/* power down the card interface if it's currently powered up */
|
|
if (tsc_device->hw_card_status == TSC_CARD_STATUS_DETECTED &&
|
|
tsc_device->card_power) {
|
|
ret = tsc_card_power_down();
|
|
if (ret != 0)
|
|
pr_err("%s: card power-down failed\n", __func__);
|
|
}
|
|
|
|
if (!tsc_iommu_bypass)
|
|
iommu_detach_group(tsc_device->iommu_info.domain,
|
|
tsc_device->iommu_info.group);
|
|
|
|
gpio_free(tsc_device->reset_cam_gpio);
|
|
|
|
tsc_power_off_buff_mode_clocks();
|
|
tsc_device_power_off();
|
|
|
|
tsc_device->num_ci_opened--;
|
|
mutex_unlock(&tsc_ci->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_mux_poll() - Perform polling on a designated wait-queue.
|
|
*
|
|
* @flip: The file pointer associated with the TSC Mux device.
|
|
* @p: The poll-table struct of the kernel.
|
|
*
|
|
* Add the TSC Mux wait-queue to the poll-table. Poll until a rate mismatch
|
|
* interrupt is received.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static unsigned int tsc_mux_poll(struct file *filp, struct poll_table_struct *p)
|
|
{
|
|
unsigned long flags;
|
|
unsigned int mask = 0;
|
|
struct tsc_mux_chdev *tsc_mux;
|
|
|
|
tsc_mux = filp->private_data;
|
|
if (!tsc_mux)
|
|
return -EINVAL;
|
|
|
|
/* register the wait queue for rate mismatch interrupt */
|
|
poll_wait(filp, &tsc_mux->poll_queue, p);
|
|
|
|
/* Setting the mask upon rate mismatch irq and clearing the flag */
|
|
spin_lock_irqsave(&tsc_mux->spinlock, flags);
|
|
if (tsc_mux->rate_interrupt) {
|
|
mask = POLLPRI;
|
|
tsc_mux->rate_interrupt = false;
|
|
}
|
|
spin_unlock_irqrestore(&tsc_mux->spinlock, flags);
|
|
|
|
return mask;
|
|
}
|
|
|
|
/**
|
|
* tsc_ci_poll() - Perform polling on a designated wait-queue.
|
|
*
|
|
* @flip: The file pointer associated with the TSC CI device.
|
|
* @p: The poll-table struct of the kernel.
|
|
*
|
|
* Add the TSC Mux wait-queue to the poll-table. Poll until a card detection
|
|
* interrupt is received.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static unsigned int tsc_ci_poll(struct file *filp, struct poll_table_struct *p)
|
|
{
|
|
unsigned int mask = 0;
|
|
|
|
struct tsc_ci_chdev *tsc_ci = filp->private_data;
|
|
if (!tsc_ci)
|
|
return -EINVAL;
|
|
|
|
/* Register the wait queue for card detection interrupt */
|
|
poll_wait(filp, &tsc_ci->poll_queue, p);
|
|
|
|
/* Setting the mask upon card detect irq and update ci card state */
|
|
if (mutex_lock_interruptible(&tsc_ci->mutex))
|
|
return -ERESTARTSYS;
|
|
if (tsc_ci->card_status != tsc_device->hw_card_status) {
|
|
mask = POLLPRI;
|
|
tsc_ci->card_status = tsc_device->hw_card_status;
|
|
}
|
|
mutex_unlock(&tsc_ci->mutex);
|
|
|
|
return mask;
|
|
}
|
|
|
|
/**
|
|
* tsc_mux_ioctl() - Handle IOCTLs sent from user-space application.
|
|
*
|
|
* @flip: The file pointer associated with the TSC Mux device.
|
|
* @cmd: The IOCTL code sent
|
|
* @arg: The IOCTL argument (if the IOCTL receives an argument)
|
|
*
|
|
* Verify the validity of the IOCTL sent and handle it by updating the
|
|
* appropriate register or calling a function that handle the IOCTL operation.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static long tsc_mux_ioctl(struct file *filp,
|
|
unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct tsc_mux_chdev *tsc_mux;
|
|
struct tsc_route tsc_route;
|
|
struct tsc_tsif_params tsif_params;
|
|
|
|
tsc_mux = filp->private_data;
|
|
if (!tsc_mux)
|
|
return -EINVAL;
|
|
|
|
if (mutex_lock_interruptible(&tsc_mux->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
switch (cmd) {
|
|
case TSC_CONFIG_ROUTE:
|
|
if (!arg || copy_from_user(&tsc_route, (void *)arg,
|
|
sizeof(struct tsc_route))) {
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
ret = tsc_route_mux(tsc_mux, tsc_route.source, tsc_route.dest);
|
|
break;
|
|
case TSC_ENABLE_INPUT:
|
|
ret = tsc_enable_disable_tsif(tsc_mux, arg, TSIF_INPUT_ENABLE);
|
|
break;
|
|
case TSC_DISABLE_INPUT:
|
|
ret = tsc_enable_disable_tsif(tsc_mux, arg, TSIF_INPUT_DISABLE);
|
|
break;
|
|
case TSC_SET_TSIF_CONFIG:
|
|
if (!arg || copy_from_user(&tsif_params, (void *)arg,
|
|
sizeof(struct tsc_tsif_params))) {
|
|
ret = -EFAULT;
|
|
goto err;
|
|
}
|
|
ret = tsc_config_tsif(tsc_mux, &tsif_params);
|
|
break;
|
|
case TSC_CLEAR_RATE_MISMATCH_IRQ:
|
|
tsc_enable_rate_irq(tsc_mux);
|
|
break;
|
|
case TSC_CICAM_SET_CLOCK:
|
|
ret = tsc_set_cicam_clk(arg);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
pr_err("%s: Unknown ioctl %i", __func__, cmd);
|
|
}
|
|
|
|
err:
|
|
mutex_unlock(&tsc_mux->mutex);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_ci_ioctl() - Handle IOCTLs sent from user-space application.
|
|
*
|
|
* @flip: The file pointer associated with the TSC CI device.
|
|
* @cmd: The IOCTL code sent
|
|
* @arg: The IOCTL argument (if the IOCTL receives an argument)
|
|
*
|
|
* Verify the validity of the IOCTL sent and handle it by updating the
|
|
* appropriate register or calling a function that handle the IOCTL operation.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static long tsc_ci_ioctl(struct file *filp,
|
|
unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
int ret = 0;
|
|
struct tsc_ci_chdev *tsc_ci;
|
|
unsigned long flags;
|
|
|
|
tsc_ci = filp->private_data;
|
|
if (!tsc_ci)
|
|
return -EINVAL;
|
|
|
|
if (mutex_lock_interruptible(&tsc_ci->mutex))
|
|
return -ERESTARTSYS;
|
|
|
|
switch (cmd) {
|
|
|
|
case TSC_CAM_RESET:
|
|
ret = tsc_reset_cam();
|
|
break;
|
|
case TSC_CICAM_PERSONALITY_CHANGE:
|
|
ret = tsc_personality_change(arg);
|
|
break;
|
|
case TSC_GET_CARD_STATUS:
|
|
spin_lock_irqsave(&tsc_ci->spinlock, flags);
|
|
tsc_ci->card_status = tsc_device->hw_card_status;
|
|
ret = __put_user(tsc_ci->card_status,
|
|
(enum tsc_card_status __user *)arg);
|
|
spin_unlock_irqrestore(&tsc_ci->spinlock, flags);
|
|
break;
|
|
case TSC_READ_CAM_MEMORY:
|
|
ret = tsc_data_transaction(tsc_ci, MEMORY_TRANSACTION,
|
|
READ_TRANSACTION, SINGLE_BYTE_MODE, arg);
|
|
break;
|
|
case TSC_WRITE_CAM_MEMORY:
|
|
ret = tsc_data_transaction(tsc_ci, MEMORY_TRANSACTION,
|
|
WRITE_TRANSACTION, SINGLE_BYTE_MODE, arg);
|
|
break;
|
|
case TSC_READ_CAM_IO:
|
|
ret = tsc_data_transaction(tsc_ci, IO_TRANSACTION,
|
|
READ_TRANSACTION, SINGLE_BYTE_MODE, arg);
|
|
break;
|
|
case TSC_WRITE_CAM_IO:
|
|
ret = tsc_data_transaction(tsc_ci, IO_TRANSACTION,
|
|
WRITE_TRANSACTION, SINGLE_BYTE_MODE, arg);
|
|
break;
|
|
case TSC_READ_CAM_BUFFER:
|
|
ret = tsc_data_transaction(tsc_ci, IO_TRANSACTION,
|
|
READ_TRANSACTION, BUFFER_MODE, arg);
|
|
break;
|
|
case TSC_WRITE_CAM_BUFFER:
|
|
ret = tsc_data_transaction(tsc_ci, IO_TRANSACTION,
|
|
WRITE_TRANSACTION, BUFFER_MODE, arg);
|
|
break;
|
|
default:
|
|
ret = -EINVAL;
|
|
pr_err("%s: Unknown ioctl %i\n", __func__, cmd);
|
|
}
|
|
|
|
mutex_unlock(&tsc_ci->mutex);
|
|
return ret;
|
|
}
|
|
|
|
/************************** Probe helper-functions **************************/
|
|
/**
|
|
* tsc_init_char_driver() - Initialize a character driver.
|
|
*
|
|
* @pcdev: A pointer to the cdev structure to initialize.
|
|
* @pfops: A pointer to the file_operations for this device.
|
|
* @device_number: A pointer that will store the device number.
|
|
* @device: A pointer that will store the new device upon success.
|
|
* @name: A string for the device's name.
|
|
*
|
|
* Create a new character device driver inside the TSC class. The new device
|
|
* is created under "/dev/<name>0".
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_init_char_driver(struct cdev *pcdev,
|
|
const struct file_operations *pfops,
|
|
dev_t *pdevice_number,
|
|
struct device *pdevice,
|
|
const char *name)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Allocate device number for the char device driver */
|
|
ret = alloc_chrdev_region(pdevice_number, 0, 1, name);
|
|
if (ret) {
|
|
pr_err("%s: alloc_chrdev_region failed: %d\n", name, ret);
|
|
goto err_devrgn;
|
|
}
|
|
|
|
/* initializing the char device structures with file operations */
|
|
cdev_init(pcdev, pfops);
|
|
pcdev->owner = THIS_MODULE;
|
|
|
|
/* adding the char device structures to the VFS */
|
|
ret = cdev_add(pcdev, *pdevice_number, 1);
|
|
if (ret != 0) {
|
|
pr_err("%s%d: cdev_add failed\n", name, MINOR(*pdevice_number));
|
|
goto err_cdev_add;
|
|
}
|
|
|
|
/* create the char devices under "/dev/" and register them to sysfs */
|
|
pdevice = device_create(tsc_class, NULL, pcdev->dev, NULL, "%s%d", name,
|
|
MINOR(*pdevice_number));
|
|
if (IS_ERR(pdevice)) {
|
|
pr_err("%s%d device_create failed\n", name,
|
|
MINOR(*pdevice_number));
|
|
ret = PTR_ERR(pdevice); /* PTR_ERR return -ENOMEM */
|
|
goto err_device_create;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_device_create:
|
|
cdev_del(pcdev);
|
|
err_cdev_add:
|
|
unregister_chrdev_region(*pdevice_number, 1);
|
|
err_devrgn:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_get_pinctrl() - Get the TSC pinctrl definitions.
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Get the pinctrl states' handles from the device tree. The function doesn't
|
|
* enforce wrong pinctrl definitions, i.e. it's the client's responsibility to
|
|
* define all the necessary states for the board being used.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_get_pinctrl(struct platform_device *pdev)
|
|
{
|
|
struct pinctrl *pinctrl;
|
|
|
|
pinctrl = devm_pinctrl_get(&pdev->dev);
|
|
if (IS_ERR(pinctrl)) {
|
|
pr_err("%s: Unable to get pinctrl handle\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
tsc_device->pinctrl_info.pinctrl = pinctrl;
|
|
|
|
/* get all the states handles */
|
|
tsc_device->pinctrl_info.disable =
|
|
pinctrl_lookup_state(pinctrl, "disable");
|
|
tsc_device->pinctrl_info.ts0 =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-0");
|
|
tsc_device->pinctrl_info.ts1 =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-1");
|
|
tsc_device->pinctrl_info.dual_ts =
|
|
pinctrl_lookup_state(pinctrl, "dual-ts");
|
|
tsc_device->pinctrl_info.pc_card =
|
|
pinctrl_lookup_state(pinctrl, "pc-card");
|
|
tsc_device->pinctrl_info.ci_card =
|
|
pinctrl_lookup_state(pinctrl, "ci-card");
|
|
tsc_device->pinctrl_info.ci_plus =
|
|
pinctrl_lookup_state(pinctrl, "ci-plus");
|
|
tsc_device->pinctrl_info.ts0_pc_card =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-0-pc-card");
|
|
tsc_device->pinctrl_info.ts0_ci_card =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-0-ci-card");
|
|
tsc_device->pinctrl_info.ts0_ci_plus =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-0-ci-plus");
|
|
tsc_device->pinctrl_info.ts1_pc_card =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-1-pc-card");
|
|
tsc_device->pinctrl_info.ts1_ci_card =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-1-ci-card");
|
|
tsc_device->pinctrl_info.ts1_ci_plus =
|
|
pinctrl_lookup_state(pinctrl, "ts-in-1-ci-plus");
|
|
tsc_device->pinctrl_info.dual_ts_pc_card =
|
|
pinctrl_lookup_state(pinctrl, "dual-ts-pc-card");
|
|
tsc_device->pinctrl_info.dual_ts_ci_card =
|
|
pinctrl_lookup_state(pinctrl, "dual-ts-ci-card");
|
|
tsc_device->pinctrl_info.dual_ts_ci_plus =
|
|
pinctrl_lookup_state(pinctrl, "dual-ts-ci-plus");
|
|
|
|
if (IS_ERR(tsc_device->pinctrl_info.disable)) {
|
|
pr_err("%s: Unable to get pinctrl disable state handle\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Basic checks to inquire what pinctrl states are available */
|
|
if (IS_ERR(tsc_device->pinctrl_info.ts0))
|
|
tsc_device->pinctrl_info.is_ts0 = false;
|
|
else
|
|
tsc_device->pinctrl_info.is_ts0 = true;
|
|
|
|
if (IS_ERR(tsc_device->pinctrl_info.ts1))
|
|
tsc_device->pinctrl_info.is_ts1 = false;
|
|
else
|
|
tsc_device->pinctrl_info.is_ts1 = true;
|
|
|
|
if (IS_ERR(tsc_device->pinctrl_info.pc_card) ||
|
|
IS_ERR(tsc_device->pinctrl_info.ci_card) ||
|
|
IS_ERR(tsc_device->pinctrl_info.ci_plus))
|
|
tsc_device->pinctrl_info.is_pcmcia = false;
|
|
else
|
|
tsc_device->pinctrl_info.is_pcmcia = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_get_regulator_bus() - Get the TSC regulator and register the bus client.
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_get_regulator_bus(struct platform_device *pdev)
|
|
{
|
|
struct msm_bus_scale_pdata *tsc_bus_pdata = NULL;
|
|
|
|
/* Reading the GDSC info */
|
|
tsc_device->gdsc = devm_regulator_get(&pdev->dev, "vdd");
|
|
if (IS_ERR(tsc_device->gdsc)) {
|
|
dev_err(&pdev->dev, "%s: Failed to get vdd power regulator\n",
|
|
__func__);
|
|
return PTR_ERR(tsc_device->gdsc);
|
|
}
|
|
|
|
/* Reading the bus platform data */
|
|
tsc_bus_pdata = msm_bus_cl_get_pdata(pdev);
|
|
if (tsc_bus_pdata == NULL) {
|
|
dev_err(&pdev->dev, "%s: Could not find the bus property. Continue anyway...\n",
|
|
__func__);
|
|
}
|
|
|
|
/* Register the bus client */
|
|
if (tsc_bus_pdata) {
|
|
tsc_device->bus_client =
|
|
msm_bus_scale_register_client(tsc_bus_pdata);
|
|
if (!tsc_device->bus_client) {
|
|
dev_err(&pdev->dev, "%s: Unable to register bus client\n",
|
|
__func__);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
devm_regulator_put(tsc_device->gdsc);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* tsc_get_irqs() - Get the TSC IRQ numbers and map the cam irq.
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Read the irq numbers from the platform device information.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_get_irqs(struct platform_device *pdev)
|
|
{
|
|
int irq;
|
|
|
|
irq = platform_get_irq_byname(pdev, "cam-cmd");
|
|
if (irq > 0) {
|
|
tsc_device->cam_cmd_irq = irq;
|
|
} else {
|
|
dev_err(&pdev->dev, "%s: Failed to get CAM_CMD IRQ = %d",
|
|
__func__, irq);
|
|
goto err;
|
|
}
|
|
|
|
irq = platform_get_irq_byname(pdev, "card-detect");
|
|
if (irq > 0) {
|
|
tsc_device->card_detection_irq = irq;
|
|
} else {
|
|
dev_err(&pdev->dev, "%s: Failed to get CARD_DETECT IRQ = %d",
|
|
__func__, irq);
|
|
goto err;
|
|
}
|
|
|
|
return 0;
|
|
err:
|
|
tsc_device->cam_cmd_irq = 0;
|
|
tsc_device->card_detection_irq = 0;
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
/**
|
|
* tsc_map_io_memory() - Map memory resources to kernel space.
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_map_io_memory(struct platform_device *pdev)
|
|
{
|
|
struct resource *registers_mem;
|
|
|
|
/* Reading memory resources */
|
|
registers_mem = platform_get_resource_byname(pdev, IORESOURCE_MEM,
|
|
"tsc-base");
|
|
if (!registers_mem) {
|
|
dev_err(&pdev->dev, "%s: Missing tsc-base MEM resource",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tsc_device->base = ioremap(registers_mem->start,
|
|
resource_size(registers_mem));
|
|
if (!tsc_device->base) {
|
|
dev_err(&pdev->dev, "%s: ioremap failed", __func__);
|
|
return -ENXIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tsc_clocks_put() - Put the clocks
|
|
*/
|
|
static void tsc_clocks_put(void)
|
|
{
|
|
if (tsc_device->ahb_clk)
|
|
clk_put(tsc_device->ahb_clk);
|
|
if (tsc_device->ci_clk)
|
|
clk_put(tsc_device->ci_clk);
|
|
if (tsc_device->ser_clk)
|
|
clk_put(tsc_device->ser_clk);
|
|
if (tsc_device->par_clk)
|
|
clk_put(tsc_device->par_clk);
|
|
if (tsc_device->cicam_ts_clk)
|
|
clk_put(tsc_device->cicam_ts_clk);
|
|
if (tsc_device->tspp2_core_clk)
|
|
clk_put(tsc_device->tspp2_core_clk);
|
|
if (tsc_device->vbif_tspp2_clk)
|
|
clk_put(tsc_device->vbif_tspp2_clk);
|
|
if (tsc_device->vbif_ahb_clk)
|
|
clk_put(tsc_device->vbif_ahb_clk);
|
|
if (tsc_device->vbif_axi_clk)
|
|
clk_put(tsc_device->vbif_axi_clk);
|
|
|
|
tsc_device->ahb_clk = NULL;
|
|
tsc_device->ci_clk = NULL;
|
|
tsc_device->ser_clk = NULL;
|
|
tsc_device->par_clk = NULL;
|
|
tsc_device->cicam_ts_clk = NULL;
|
|
tsc_device->tspp2_core_clk = NULL;
|
|
tsc_device->vbif_tspp2_clk = NULL;
|
|
tsc_device->vbif_ahb_clk = NULL;
|
|
tsc_device->vbif_axi_clk = NULL;
|
|
}
|
|
|
|
/**
|
|
* tsc_clocks_get() - Get the TSC clocks
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_clocks_get(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
tsc_device->ahb_clk = clk_get(&pdev->dev, "bcc_tsc_ahb_clk");
|
|
if (IS_ERR(tsc_device->ahb_clk)) {
|
|
pr_err("%s: Failed to get bcc_tsc_ahb_clk\n", __func__);
|
|
ret = PTR_ERR(tsc_device->ahb_clk);
|
|
goto ahb_err;
|
|
}
|
|
|
|
tsc_device->ci_clk = clk_get(&pdev->dev, "bcc_tsc_ci_clk");
|
|
if (IS_ERR(tsc_device->ci_clk)) {
|
|
pr_err("%s: Failed to get bcc_tsc_ci_clk\n", __func__);
|
|
ret = PTR_ERR(tsc_device->ci_clk);
|
|
goto ci_err;
|
|
}
|
|
|
|
tsc_device->ser_clk = clk_get(&pdev->dev, "bcc_tsc_ser_clk");
|
|
if (IS_ERR(tsc_device->ser_clk)) {
|
|
pr_err("%s: Failed to get bcc_tsc_ser_clk\n", __func__);
|
|
ret = PTR_ERR(tsc_device->ser_clk);
|
|
goto ser_err;
|
|
}
|
|
|
|
tsc_device->par_clk = clk_get(&pdev->dev, "bcc_tsc_par_clk");
|
|
if (IS_ERR(tsc_device->par_clk)) {
|
|
pr_err("%s: Failed to get bcc_tsc_par_clk", __func__);
|
|
ret = PTR_ERR(tsc_device->par_clk);
|
|
goto par_err;
|
|
}
|
|
|
|
tsc_device->cicam_ts_clk = clk_get(&pdev->dev, "bcc_tsc_cicam_ts_clk");
|
|
if (IS_ERR(tsc_device->cicam_ts_clk)) {
|
|
pr_err("%s: Failed to get bcc_tsc_cicam_ts_clk", __func__);
|
|
ret = PTR_ERR(tsc_device->cicam_ts_clk);
|
|
goto cicam_err;
|
|
}
|
|
|
|
tsc_device->tspp2_core_clk = clk_get(&pdev->dev, "bcc_tspp2_core_clk");
|
|
if (IS_ERR(tsc_device->tspp2_core_clk)) {
|
|
pr_err("%s: Failed to get bcc_tspp2_core_clk", __func__);
|
|
ret = PTR_ERR(tsc_device->tspp2_core_clk);
|
|
goto tspp2_err;
|
|
}
|
|
|
|
tsc_device->vbif_tspp2_clk = clk_get(&pdev->dev, "bcc_vbif_tspp2_clk");
|
|
if (IS_ERR(tsc_device->vbif_tspp2_clk)) {
|
|
pr_err("%s: Failed to get bcc_vbif_tspp2_clk", __func__);
|
|
ret = PTR_ERR(tsc_device->vbif_tspp2_clk);
|
|
goto vbif_tspp2_err;
|
|
}
|
|
|
|
tsc_device->vbif_ahb_clk = clk_get(&pdev->dev, "iface_vbif_clk");
|
|
if (IS_ERR(tsc_device->vbif_ahb_clk)) {
|
|
pr_err("%s: Failed to get bcc_vbif_ahb_clk", __func__);
|
|
ret = PTR_ERR(tsc_device->vbif_ahb_clk);
|
|
goto vbif_ahb_err;
|
|
}
|
|
|
|
tsc_device->vbif_axi_clk = clk_get(&pdev->dev, "vbif_core_clk");
|
|
if (IS_ERR(tsc_device->vbif_axi_clk)) {
|
|
pr_err("%s: Failed to get bcc_vbif_axi_clk", __func__);
|
|
ret = PTR_ERR(tsc_device->vbif_axi_clk);
|
|
goto vbif_axi_err;
|
|
}
|
|
|
|
return ret;
|
|
|
|
vbif_axi_err:
|
|
tsc_device->vbif_axi_clk = NULL;
|
|
clk_put(tsc_device->vbif_ahb_clk);
|
|
vbif_ahb_err:
|
|
tsc_device->vbif_ahb_clk = NULL;
|
|
clk_put(tsc_device->vbif_tspp2_clk);
|
|
vbif_tspp2_err:
|
|
tsc_device->vbif_tspp2_clk = NULL;
|
|
clk_put(tsc_device->tspp2_core_clk);
|
|
tspp2_err:
|
|
tsc_device->tspp2_core_clk = NULL;
|
|
clk_put(tsc_device->cicam_ts_clk);
|
|
cicam_err:
|
|
tsc_device->cicam_ts_clk = NULL;
|
|
clk_put(tsc_device->par_clk);
|
|
par_err:
|
|
tsc_device->par_clk = NULL;
|
|
clk_put(tsc_device->ser_clk);
|
|
ser_err:
|
|
tsc_device->ser_clk = NULL;
|
|
clk_put(tsc_device->ci_clk);
|
|
ci_err:
|
|
tsc_device->ci_clk = NULL;
|
|
clk_put(tsc_device->ahb_clk);
|
|
ahb_err:
|
|
tsc_device->ahb_clk = NULL;
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_free_iommu_info() - Free IOMMU information.
|
|
*/
|
|
static void tsc_free_iommu_info(void)
|
|
{
|
|
if (tsc_device->iommu_info.group) {
|
|
iommu_group_put(tsc_device->iommu_info.group);
|
|
tsc_device->iommu_info.group = NULL;
|
|
}
|
|
|
|
if (tsc_device->iommu_info.ion_client) {
|
|
ion_client_destroy(tsc_device->iommu_info.ion_client);
|
|
tsc_device->iommu_info.ion_client = NULL;
|
|
}
|
|
|
|
tsc_device->iommu_info.domain = NULL;
|
|
tsc_device->iommu_info.domain_num = -1;
|
|
tsc_device->iommu_info.partition_num = -1;
|
|
}
|
|
|
|
/**
|
|
* tsc_get_iommu_info() - Get IOMMU information.
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_get_iommu_info(struct platform_device *pdev)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* Create a new ION client used by tsc ci to allocate memory */
|
|
tsc_device->iommu_info.ion_client = msm_ion_client_create("tsc_client");
|
|
if (IS_ERR_OR_NULL(tsc_device->iommu_info.ion_client)) {
|
|
pr_err("%s: error in ion_client_create", __func__);
|
|
ret = PTR_ERR(tsc_device->iommu_info.ion_client);
|
|
if (!ret)
|
|
ret = -ENOMEM;
|
|
tsc_device->iommu_info.ion_client = NULL;
|
|
goto err_client;
|
|
}
|
|
|
|
/* Find the iommu group by the name obtained from the device tree */
|
|
tsc_device->iommu_info.group =
|
|
iommu_group_find(tsc_device->iommu_info.iommu_group_name);
|
|
if (!tsc_device->iommu_info.group) {
|
|
pr_err("%s: error in iommu_group_find", __func__);
|
|
ret = -EINVAL;
|
|
goto err_group;
|
|
}
|
|
|
|
/* Get the domain associated with the iommu group */
|
|
tsc_device->iommu_info.domain =
|
|
iommu_group_get_iommudata(tsc_device->iommu_info.group);
|
|
if (IS_ERR_OR_NULL(tsc_device->iommu_info.domain)) {
|
|
pr_err("%s: iommu_group_get_iommudata failed", __func__);
|
|
ret = -EINVAL;
|
|
goto err_domain;
|
|
}
|
|
|
|
/* Get the domain number */
|
|
tsc_device->iommu_info.domain_num =
|
|
msm_find_domain_no(tsc_device->iommu_info.domain);
|
|
|
|
return ret;
|
|
|
|
err_domain:
|
|
iommu_group_put(tsc_device->iommu_info.group);
|
|
tsc_device->iommu_info.group = NULL;
|
|
err_group:
|
|
ion_client_destroy(tsc_device->iommu_info.ion_client);
|
|
tsc_device->iommu_info.ion_client = NULL;
|
|
err_client:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_parse_dt() - Parse device-tree data and save it.
|
|
*
|
|
* @pdev: A pointer to the TSC platform device.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int tsc_parse_dt(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
struct device_node *iommu_pnode;
|
|
int ret;
|
|
|
|
/* Check that power regulator property exist */
|
|
if (!of_get_property(node, "vdd-supply", NULL)) {
|
|
dev_err(&pdev->dev, "%s: Could not find vdd-supply property\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Reading IOMMU group label by obtaining the group's phandle */
|
|
iommu_pnode = of_parse_phandle(node, "qcom,iommu-group", 0);
|
|
if (!iommu_pnode) {
|
|
dev_err(&pdev->dev, "%s: Couldn't find iommu-group property\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
ret = of_property_read_string(iommu_pnode, "label",
|
|
&tsc_device->iommu_info.iommu_group_name);
|
|
of_node_put(iommu_pnode);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "%s: Couldn't find label property of the IOMMU group, err=%d\n",
|
|
__func__, ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Reading IOMMU partition */
|
|
ret = of_property_read_u32(node, "qcom,iommu-partition",
|
|
&tsc_device->iommu_info.partition_num);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "%s: Couldn't find iommu-partition property, err=%d\n",
|
|
__func__, ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Reading reset cam gpio */
|
|
tsc_device->reset_cam_gpio = of_get_named_gpio(node,
|
|
"qcom,tsc-reset-cam-gpio", 0);
|
|
if (tsc_device->reset_cam_gpio < 0) {
|
|
dev_err(&pdev->dev, "%s: Couldn't find qcom,tsc-reset-cam-gpio property\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* TSC Mux file operations */
|
|
static const struct file_operations tsc_mux_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tsc_mux_open,
|
|
.poll = tsc_mux_poll,
|
|
.release = tsc_mux_release,
|
|
.unlocked_ioctl = tsc_mux_ioctl,
|
|
};
|
|
|
|
/* TSC CI file operations */
|
|
static const struct file_operations tsc_ci_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = tsc_ci_open,
|
|
.poll = tsc_ci_poll,
|
|
.release = tsc_ci_release,
|
|
.unlocked_ioctl = tsc_ci_ioctl,
|
|
};
|
|
|
|
|
|
/************************ Device driver probe function ************************/
|
|
static int msm_tsc_probe(struct platform_device *pdev)
|
|
{
|
|
int ret;
|
|
|
|
tsc_device = kzalloc(sizeof(struct tsc_device), GFP_KERNEL);
|
|
if (!tsc_device) {
|
|
pr_err("%s: Unable to allocate memory for struct\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* get information from device tree */
|
|
if (pdev->dev.of_node) {
|
|
ret = tsc_parse_dt(pdev);
|
|
if (ret != 0) {
|
|
pr_err("%s: devicetree data not available", __func__);
|
|
ret = -EINVAL;
|
|
goto err_dt;
|
|
}
|
|
} else { /* else - devicetree is not found */
|
|
pr_err("%s: devicetree data is missing", __func__);
|
|
ret = -EINVAL;
|
|
goto err_dt;
|
|
}
|
|
|
|
/* set up references */
|
|
tsc_device->pdev = pdev;
|
|
platform_set_drvdata(pdev, tsc_device);
|
|
|
|
/* init iommu client, group and domain */
|
|
if (!tsc_iommu_bypass) {
|
|
ret = tsc_get_iommu_info(pdev);
|
|
if (ret != 0)
|
|
return ret;
|
|
}
|
|
|
|
/* Map clocks */
|
|
ret = tsc_clocks_get(pdev);
|
|
if (ret != 0)
|
|
goto err_clocks_get;
|
|
|
|
/* map registers memory */
|
|
ret = tsc_map_io_memory(pdev);
|
|
if (ret != 0)
|
|
goto err_map_io;
|
|
|
|
/* map irqs */
|
|
ret = tsc_get_irqs(pdev);
|
|
if (ret != 0)
|
|
goto err_map_irqs;
|
|
|
|
/* get regulators and bus */
|
|
ret = tsc_get_regulator_bus(pdev);
|
|
if (ret != 0)
|
|
goto err_get_regulator_bus;
|
|
|
|
/* get pinctrl */
|
|
ret = tsc_get_pinctrl(pdev);
|
|
if (ret != 0)
|
|
goto err_pinctrl;
|
|
|
|
/* creating the tsc device's class */
|
|
tsc_class = class_create(THIS_MODULE, "tsc");
|
|
if (IS_ERR(tsc_class)) {
|
|
ret = PTR_ERR(tsc_class);
|
|
pr_err("%s: Error creating class: %d\n", __func__, ret);
|
|
goto err_class;
|
|
}
|
|
|
|
/* Initialize and register mux char device driver */
|
|
ret = tsc_init_char_driver(&tsc_device->mux_chdev.cdev, &tsc_mux_fops,
|
|
&tsc_device->mux_device_number, tsc_device->device_mux,
|
|
"tsc_mux");
|
|
if (ret != 0)
|
|
goto err_chdev_mux;
|
|
|
|
/* Initialize and register ci char device drivers */
|
|
ret = tsc_init_char_driver(&tsc_device->ci_chdev.cdev, &tsc_ci_fops,
|
|
&tsc_device->ci_device_number, tsc_device->device_ci,
|
|
"tsc_ci");
|
|
if (ret != 0)
|
|
goto err_chdev_ci;
|
|
|
|
/* Init char device counters */
|
|
tsc_device->num_device_open = 0;
|
|
tsc_device->num_mux_opened = 0;
|
|
tsc_device->num_ci_opened = 0;
|
|
|
|
/* Init char device mutexes and completion structs */
|
|
mutex_init(&tsc_device->mux_chdev.mutex);
|
|
mutex_init(&tsc_device->ci_chdev.mutex);
|
|
mutex_init(&tsc_device->mutex);
|
|
init_completion(&tsc_device->ci_chdev.transaction_complete);
|
|
init_completion(&tsc_device->ci_chdev.transaction_finish);
|
|
|
|
/* Init debugfs support */
|
|
tsc_debugfs_init();
|
|
|
|
return ret;
|
|
|
|
err_chdev_ci:
|
|
device_destroy(tsc_class, tsc_device->mux_chdev.cdev.dev);
|
|
cdev_del(&tsc_device->mux_chdev.cdev);
|
|
err_chdev_mux:
|
|
class_destroy(tsc_class);
|
|
err_class:
|
|
err_pinctrl:
|
|
if (tsc_device->bus_client)
|
|
msm_bus_scale_unregister_client(tsc_device->bus_client);
|
|
|
|
devm_regulator_put(tsc_device->gdsc);
|
|
err_get_regulator_bus:
|
|
err_map_irqs:
|
|
iounmap(tsc_device->base);
|
|
err_map_io:
|
|
tsc_clocks_put();
|
|
err_clocks_get:
|
|
tsc_free_iommu_info();
|
|
err_dt:
|
|
kfree(tsc_device);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*********************** Device driver remove function ***********************/
|
|
static int msm_tsc_remove(struct platform_device *pdev)
|
|
{
|
|
/* Removing debugfs support */
|
|
tsc_debugfs_exit();
|
|
|
|
/* Destroying the char device mutexes */
|
|
mutex_destroy(&tsc_device->mux_chdev.mutex);
|
|
mutex_destroy(&tsc_device->ci_chdev.mutex);
|
|
|
|
/* unregistering and deleting the tsc-ci char device driver*/
|
|
device_destroy(tsc_class, tsc_device->ci_chdev.cdev.dev);
|
|
cdev_del(&tsc_device->ci_chdev.cdev);
|
|
|
|
/* unregistering and deleting the tsc-mux char device driver*/
|
|
device_destroy(tsc_class, tsc_device->mux_chdev.cdev.dev);
|
|
cdev_del(&tsc_device->mux_chdev.cdev);
|
|
|
|
/* Unregistering the char devices */
|
|
unregister_chrdev_region(tsc_device->ci_device_number, 1);
|
|
unregister_chrdev_region(tsc_device->mux_device_number, 1);
|
|
|
|
/* Removing the tsc class*/
|
|
class_destroy(tsc_class);
|
|
|
|
/* Unregister the bus client and the regulator */
|
|
if (tsc_device->bus_client)
|
|
msm_bus_scale_unregister_client(tsc_device->bus_client);
|
|
|
|
devm_regulator_put(tsc_device->gdsc);
|
|
|
|
/* Unmapping the io memory */
|
|
iounmap(tsc_device->base);
|
|
|
|
/* Releasing the clocks */
|
|
tsc_clocks_put();
|
|
|
|
/* Releasing the iommu info */
|
|
if (!tsc_iommu_bypass)
|
|
tsc_free_iommu_info();
|
|
|
|
/* Releasing the memory allocated for the TSC device struct */
|
|
kfree(tsc_device);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*********************** Platform driver information ***********************/
|
|
static struct of_device_id msm_match_table[] = {
|
|
{.compatible = "qcom,msm-tsc"},
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver msm_tsc_driver = {
|
|
.probe = msm_tsc_probe,
|
|
.remove = msm_tsc_remove,
|
|
.driver = {
|
|
.name = "msm_tsc",
|
|
.of_match_table = msm_match_table,
|
|
},
|
|
};
|
|
|
|
/**
|
|
* tsc_init() - TSC driver module init function.
|
|
*
|
|
* Return 0 on success, error value otherwise.
|
|
*/
|
|
static int __init tsc_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
/* register the driver, and check hardware */
|
|
ret = platform_driver_register(&msm_tsc_driver);
|
|
if (ret) {
|
|
pr_err("%s: platform_driver_register failed: %d\n", __func__,
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* tsc_exit() - TSC driver module exit function.
|
|
*/
|
|
static void __exit tsc_exit(void)
|
|
{
|
|
platform_driver_unregister(&msm_tsc_driver);
|
|
}
|
|
|
|
module_init(tsc_init);
|
|
module_exit(tsc_exit);
|
|
|
|
MODULE_DESCRIPTION("TSC platform device and two char devs: mux and ci");
|
|
MODULE_LICENSE("GPL v2");
|