msm: ensigma uccp330: Support ensigma uccp330 HW unit

ENSIGMA UCCP330 is an integrated multi-standard broadcast demodulator.
It locks on broadcast RF signal and produces MPEG-2 transport stream.

Change-Id: I96d731e85d167a95a1f75da63bd827af302fc4dd
Signed-off-by: Mickey Mendlin <mmendl@codeaurora.org>
This commit is contained in:
Mickey Mendlin 2013-11-13 11:31:01 +02:00
parent 2c8ba2e740
commit 1ee9bd0756
7 changed files with 925 additions and 0 deletions

View File

@ -0,0 +1,152 @@
Introduction
============
UCCP330 Driver
The UCCP330 is a broadcast demodulator core. It translates the RF signal into a
digital data bit stream (called TS - transport stream). It is a part of the SoC
and is mapped into Application Processor memory space. The UCCP330 includes
internal processors and it should be reloaded with different firmware images
according to the TV standard acquired. The kernel driver is a thin layer used
only for tunneling the communication protocol between Application processor and
UCCP330.
Hardware description
====================
UCCP330 uses several memory areas for its function:
- External RAM - part of DDR accessible from both the Application processor and
UCCP330 itself
- GRAM - internal RAM, fully mapped into Application processor memory space and
R/W accessible
- CoreRAM - internal RAM of UCCP330, accessible via special set of UCCP330
control registers - only single word at a time
+---------------+ +----------------------------------+ +--------------+
| | | UCCP330 | | DDR |
| | |----------------------------------| |--------------|
| +-------------------------------------------+ | |
| | | +--------------+ | | | |
| +--------------------->| | | | | |
| Application | | | | | | +--------------+
| | | | GRAM | | +---> |
| Processor | | | | | | |
| | | +-----------+ +--------------+ | | External |
| | | | UCCP330 | | | +-------> RAM |
| +------>| Control +--> Core RAM | | | |
| | | | Registers| | | | +--------------+
| | | | | | | | | |
| <----- +-----------+ +--------------+ | | |
| | IRQ| | | |
+---------------+ +----------------------------------+ +--------------+
The role of this driver is to provide to upper layer software access to all
of these resourses and to service the IRQ.
Software description
====================
Driver initialization
---------------------
The driver's probe function is invoked if there is a matching device tree node
(or platform device). The probe function gets the required memory resources
(i.e., register address spaces) and maps them to kernel space for the driver's
use. The probe function also requests the IRQ. Finally, the probe function
resets all HW registers to appropriate default values, and resets all the
required software structures.
Character device
-----------------
The driver provides character device abstraction to the user-space. With ability
to read/write required amount of data. Helper ioctl functions shall define the
region to be written/read from.
The file will be created as /dev/uccp330.
Design
======
The UCCP330 driver is a regular Linux platform driver designed to support the
UCCP330 HW available on specific SoCs.
The driver provides character device API to user space driver for 2 tasks:
- Communication protocol
- Firmware download
The driver tunnels the low level hardware resources access while the logic is
being implemented at the user space driver.
Power Management
================
The UCCP330 prevents the CPU from sleeping while it is open, because by opening
the UCCP330 device, the CPU instructs it to stream digital data of a TV
broadcast into the SoC modules. Because the CPU is responsible for processing
this data it is not logical for it to go into suspend. That is why when UCCP330
device is open, it uses a wakeup_source to instruct the CPU to stay awake as
long as it's active.
SMP/multi-core
==============
The driver uses reference counter protected by spinlock, in order to prevent
more than one instance from being open.
Security
========
None.
Performance
===========
Control operations are not considered as performance critical.
Most of the control operations are assumed to be fairly uncommon.
Interface
=========
User-space API
--------------
The driver creates user-space device at /dev/uccp330
- open
- close
- ioctl
UCCP330_IOCTL_RW - Read/Write control register
UCCP330_IOCTL_SET_REGION - Point to active region for read\write
commands
UCCP330_IOCTL_RESET - reset the UCCP330 core
- read - read specified amount of bytes from one of the UCCP330 memory
regions pointed by last call of ioctl
- write- write specified amount of bytes from one of the UCCP330 memory
regions pointed by last call of ioctl
This function set is sufficient for full operation of UCCP330 by user-space
software.
Driver parameters
=================
The UCCP330 driver does not support any module parameters. Platform-dependent
parameters (e.g., IRQ numbers) are provided to the driver via the device tree
mechanism or the platform device data mechanism.
Config options
==============
To enable the driver, set CONFIG_UCCP330 to y (built-in) or m (kernel module)
in the kernel configuration menu.
Dependencies
============
None
User space utilities
====================
UCCP330.so - user mode driver
Other
=====
None.
Known issues
============
None.
To do
=====
None.

View File

@ -0,0 +1,29 @@
Ensigma UCCP330 - Broadcast demodulator
Ensigma UCCP330 is a multistandard broadcast demodulator.
For information on the Ensigma UCCP330 driver, please refer to the
Ensigma UCCP330 driver.
documentation: Documentation/arm/msm/ensigma_uccp330.txt.
The devicetree representation of the Ensigma UCCP330 block should be:
Required properties:
- compatible: "qti,msm-demod"
- reg: physical memory base addresses and sizes for the following:
Ensigma UCCP330, TOP BCSS
- reg-names: names of the memory regions:
msm-demod, top-bcss
- vdd-supply
GDSC - gdsc_bcss
Example:
demod: msm-demod@fc600000 {
compatible = "qti,msm-demod";
reg = <0xfc600000 0xd0008>,<0xfc747000 0x1000>;
reg-names = "msm-demod", "top-bcss";
vdd-supply = <&gdsc_bcss>;
};

View File

@ -24,6 +24,16 @@ config TSPP2
playback from memory and recording.
This can also be compiled as a loadable module.
config ENSIGMA_UCCP_330
depends on ARCH_MPQ8092
tristate "ENSIGMA UCCP330 (Internal broadcast demodulator) Support"
---help---
Internal broadcast demodulator is used in translation of RF
broadcast transmitions into MPEG transport stream digital protocol.
The driver is a thin communication layer that allows access
to UCCP330 internal memory in order to support high level protocol.
This can also be compiled as a loadable module.
config CI_BRIDGE_SPI
depends on SPI_QUP
tristate "CI Bridge SPI Driver Support"

View File

@ -6,3 +6,4 @@ obj-$(CONFIG_TSPP) += tspp.o
obj-$(CONFIG_TSPP2) += tspp2.o
obj-$(CONFIG_CI_BRIDGE_SPI) += ci-bridge-spi.o
obj-$(CONFIG_TSC) += tsc.o
obj-$(CONFIG_ENSIGMA_UCCP_330) += ensigma_uccp330.o

View File

@ -0,0 +1,697 @@
/* Copyright (c) 2013, 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/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/bitops.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/kernel.h> /* Only for KERN_INFO */
#include <linux/err.h> /* Error macros */
#include <linux/fs.h>
#include <linux/init.h> /* Needed for the macros */
#include <linux/io.h> /* IO macros */
#include <linux/device.h> /* Device drivers need this */
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/ensigma_uccp330.h>
#include <linux/platform_device.h>
#include <linux/errno.h>
#include <linux/regulator/consumer.h>
#include <mach/subsystem_restart.h>
#include <mach/subsystem_notif.h>
#include <mach/msm_bus_board.h>
#include <mach/msm_bus.h>
#include <mach/clk.h>
#define DRV_NAME_DEMOD "demod"
#define DRVDBG(fmt, args...)\
pr_debug(DRV_NAME_DEMOD " %s():%d " fmt, __func__, __LINE__, ## args)
#define DRVERR(fmt, args...)\
pr_err(DRV_NAME_DEMOD " %s():%d " fmt, __func__, __LINE__, ## args)
#define BASE_G32 0xB7000000
#define BASE_G24 0xB4000000
#define BASE_EXT 0xB0000000
#define EXTRAM_LIMIT 0x600000
#define GRAM_LIMIT 0x90000
#define META_RG 0x02000000
#define LOW20 0xFFFFF
#define TOP8 0xFF000000
#define TOP12 0xFFF00000
#define NIBLE_PTR 0xD0004
#define ACCESS_OFFS 0x40000
#define IA_VALUE 0x3E040
#define IA_ADDR 0x3E080
#define IA_STAT 0x3E0C0
#define BCSS_VBIF 0xE0
#define BCSSADDR ((unsigned int)drv->bcss_regs)
#define subsys_to_drv(d) container_of(d, struct venus_data, subsys_desc)
/* forward declarations */
static ssize_t demod_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t demod_open(struct inode *inode, struct file *filp);
static long demod_ioctl(struct file *, unsigned int, unsigned long);
static ssize_t demod_write(struct file *, const char *, size_t, loff_t *);
static int demod_release(struct inode *, struct file *);
/**
* struct demod_clk_desc - demod clock descriptor
*
* @name: DT entry
* @enable: should this driver enable or not
* @rate: should this driver set rate or not
*/
struct demod_clk_desc {
const char *name;
int enable;
int rate;
};
static const struct demod_clk_desc demod_clocks[] = {
{"core_clk_src", 0, 1},
{"core_clk", 1, 0},
{"core_x2_clk", 1, 1},
{"core_div2_clk", 1, 1},
{"iface_wrap_clk", 1, 1},
{"iface_clk", 1, 1},
{"bcc_vbif_dem_core_clk", 1, 0},
{"vbif_core_clk", 1, 1},
{"iface_vbif_clk", 1, 1},
};
/**
* struct demod_data - demod device descriptor
*
* @subsys: sub-system habdle for pil operaion
* @subsys_desc: subsystem descriptor
* @gdsc: pointer to gdsc
* @dev: pointer to latform device
* @clks: array of device clocks
* @cdev: char device
* @node device tree node
* @bus_perf_client: client for bus voting
* @wakeup_src: wake-up source
* @bcss_regs: virtual address of bcss registers
* @top_bcss: virtual address of top bcss registers
* @ext_ram: virtual_address of external demod ram
* @read_base: current read base for read() operation
* @write_base: currentt write base for write() operations
* @mutex: A mutex for mutual exclusion between API calls.
* @ref_counter reference counter
*/
struct demod_data {
struct subsys_device *subsys;
struct subsys_desc subsys_desc;
struct regulator *gdsc;
struct device *dev;
struct clk *clks[ARRAY_SIZE(demod_clocks)];
struct cdev cdev;
struct device_node *node;
u32 bus_perf_client;
struct wakeup_source wakeup_src;
void *bcss_regs;
void *top_bcss;
void *ext_ram;
unsigned int read_base;
unsigned int write_base;
struct mutex mutex;
int ref_counter;
};
static struct class *demod_class;
static dev_t demod_minor; /* next minor number to assign */
static const struct file_operations demod_fops = {
.owner = THIS_MODULE,
.read = demod_read,
.write = demod_write,
.open = demod_open,
.release = demod_release,
.unlocked_ioctl = demod_ioctl
};
/**
* meta_indirect_read() - read core registers via indirect IF.
*
* @drv: demod device
* addr: core reg address
* pval: pointer to store value
*/
static int meta_indirect_read(struct demod_data *drv, u32 addr, u32 *pval)
{
int i;
if (!pval)
return -EINVAL;
for (i = 0; i < 100 &&
((readl_relaxed(BCSSADDR + IA_STAT) & 0x1000000) == 0); i++)
udelay(10);
if (i == 100) {
DRVDBG("ioctl rw EACCES timeout\n");
return -EACCES;
}
writel_relaxed(addr | 0x001, BCSSADDR + IA_ADDR);
for (i = 0; i < 100 &&
((readl_relaxed(BCSSADDR + IA_ADDR) & 0x1) != 0); i++)
udelay(10);
if (i == 100) {
DRVDBG("ioctl rw EACCES timeout\n");
return -EACCES;
}
*pval = readl_relaxed(BCSSADDR + IA_VALUE);
return 0;
}
/**
* meta_indirect_write() - write core registers via indirect IF.
*
* @drv: demod device
* addr: core reg address
* val: value to write
*/
static int meta_indirect_write(struct demod_data *drv, u32 addr, u32 val)
{
int i;
for (i = 0; i < 100 &&
((readl_relaxed(BCSSADDR + IA_STAT) & 0x1000000) == 0); i++)
udelay(10);
if (i == 100) {
DRVDBG("ioctl rw EACCES timeout\n");
return -EACCES;
}
writel_relaxed(addr, BCSSADDR + IA_ADDR);
writel_relaxed(val, BCSSADDR + IA_VALUE);
for (i = 0; i < 100 &&
((readl_relaxed(BCSSADDR + IA_STAT) & 0x40000) != 0); i++)
udelay(10);
if (i == 100) {
DRVDBG("ioctl rw EACCES timeout\n");
return -EACCES;
}
return 0;
}
/**
* demod_verify_access() - Verify demod memory access.
*
* @addr: access address
* @size: size
*/
static int demod_verify_access(unsigned int addr, unsigned int size)
{
if ((addr & TOP8) == BASE_EXT) {
if (addr - BASE_EXT + size > EXTRAM_LIMIT)
return -EACCES;
} else if (((addr & LOW20) + size) > GRAM_LIMIT)
return -EACCES;
return 0;
}
/**
* demod_clock_setup() - Get clocks and set rates.
*
* @dev: DEMOD device.
*/
static int demod_clock_setup(struct device *dev)
{
struct demod_data *drv = dev_get_drvdata(dev);
int i;
for (i = 0; i < ARRAY_SIZE(drv->clks); i++) {
drv->clks[i] = devm_clk_get(dev, demod_clocks[i].name);
if (IS_ERR(drv->clks[i])) {
DRVERR("failed to get %s\n",
demod_clocks[i].name);
return PTR_ERR(drv->clks[i]);
}
DRVDBG("Clock %s current rate %lu setting %lu\n",
demod_clocks[i].name, clk_get_rate(drv->clks[i]),
clk_round_rate(drv->clks[i], 0));
/* Make sure rate-settable clocks' rates are set */
if (demod_clocks[i].rate && clk_get_rate(drv->clks[i]) == 0)
clk_set_rate(drv->clks[i],
clk_round_rate(drv->clks[i], 0));
}
return 0;
}
/**
* demod_clock_prepare_enable() - enable clocks.
*
* @dev: DEMOD device.
*/
static int demod_clock_prepare_enable(struct device *dev)
{
struct demod_data *drv = dev_get_drvdata(dev);
int rc;
int i;
for (i = 0; i < ARRAY_SIZE(drv->clks); i++) {
if (demod_clocks[i].enable) {
rc = clk_prepare_enable(drv->clks[i]);
if (rc) {
DRVERR("failed to enable %s\n",
demod_clocks[i].name);
for (i--; i >= 0; i--)
if (demod_clocks[i].enable)
clk_disable_unprepare(
drv->clks[i]);
return rc;
}
}
}
return 0;
}
/**
* demod_clock_disable_unprepare() - disable clocks.
*
* @dev: DEMOD device.
*/
static void demod_clock_disable_unprepare(struct device *dev)
{
struct demod_data *drv = dev_get_drvdata(dev);
int i;
for (i = 0; i < ARRAY_SIZE(drv->clks); i++)
clk_disable_unprepare(drv->clks[i]);
}
/**
* demod_read() - read from DEMOD memory.
*
* @filp: file pointer.
* @buf: buffer to read to
* @count: bytes to read
* @f_pos: internal offset
*/
static ssize_t demod_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
unsigned int base = 0;
struct demod_data *drv = filp->private_data;
int rc;
rc = demod_verify_access(drv->read_base, count);
if (rc)
return rc;
if ((drv->read_base & TOP8) == BASE_EXT) {
base = (unsigned int)drv->ext_ram;
} else {
writel_relaxed((drv->read_base & TOP12) >> 20,
drv->bcss_regs + NIBLE_PTR);
base = BCSSADDR + ACCESS_OFFS + (drv->read_base & LOW20);
}
if (copy_to_user((void *)buf, (void *)base, count))
return -EACCES;
return count;
}
/**
* demod_open() - open demod character device.
*
* @inode: inode.
* @filp: file pointer
*/
static ssize_t demod_open(struct inode *inode, struct file *filp)
{
struct demod_data *drv;
int rc;
drv = container_of(inode->i_cdev, struct demod_data, cdev);
filp->private_data = drv;
rc = regulator_enable(drv->gdsc);
if (rc) {
DRVERR("GDSC enable failed\n");
goto err_regulator;
}
rc = demod_clock_prepare_enable(drv->dev);
if (rc) {
DRVERR("clock prepare and enable failed\n");
goto err_clock;
}
if (mutex_lock_interruptible(&drv->mutex)) {
rc = -ERESTARTSYS;
goto err_mutex;
}
if (drv->ref_counter++ == 0) {
__pm_stay_awake(&drv->wakeup_src);
/* SET GRAM mapping to BCSS+0x40000 */
writel_relaxed((BASE_G32 >> 20),
drv->bcss_regs + NIBLE_PTR);
drv->read_base = drv->write_base = 0;
}
mutex_unlock(&drv->mutex);
return 0;
err_mutex:
demod_clock_disable_unprepare(drv->dev);
err_clock:
regulator_disable(drv->gdsc);
err_regulator:
return rc;
}
/**
* demod_close() - close demod character device.
*
* @inode: inode.
* @filp: file pointer
*/
int demod_release(struct inode *inode, struct file *filp)
{
struct demod_data *drv;
drv = container_of(inode->i_cdev, struct demod_data, cdev);
demod_clock_disable_unprepare(drv->dev);
regulator_disable(drv->gdsc);
mutex_lock(&drv->mutex);
if (--drv->ref_counter == 0)
__pm_relax(&drv->wakeup_src);
mutex_unlock(&drv->mutex);
return 0;
}
/**
* demod_ioctl() - ioctl operations with demod character device.
*
* @param0: parameter I
* @param1: parameter II
*/
static long demod_ioctl(struct file *filp,
unsigned int param0, unsigned long param1)
{
struct demod_rw rw;
struct demod_set_region sr;
unsigned int extern_addr = 0;
int rc = 0;
struct demod_data *drv = filp->private_data;
switch (param0) {
/* Read/Write single 32 bit register from any DEMOD region */
case DEMOD_IOCTL_RW:
if (copy_from_user(&rw, (void *)param1,
sizeof(struct demod_rw)) != 0)
return -EACCES;
rc = demod_verify_access(rw.addr, sizeof(unsigned int));
if (rc)
return rc;
if ((rw.addr & TOP8) == META_RG) {
extern_addr = BCSSADDR + rw.addr - META_RG;
} else if ((rw.addr & TOP8) == BASE_G24 ||
(rw.addr & TOP8) == BASE_G32) {
writel_relaxed((rw.addr & TOP12) >> 20,
drv->bcss_regs + NIBLE_PTR);
extern_addr = BCSSADDR +
ACCESS_OFFS + (rw.addr & LOW20);
} else if ((rw.addr & TOP8) == BASE_EXT) {
extern_addr =
(unsigned int)drv->ext_ram + (rw.addr & LOW20);
}
if (extern_addr != 0) {
if (rw.dir == 1) {
rw.value = readl_relaxed(extern_addr);
if (copy_to_user((void *)param1, &rw,
sizeof(struct demod_rw)) != 0) {
DRVDBG("ioctl rw EACCES\n");
return -EACCES;
}
} else {
writel_relaxed(rw.value, extern_addr);
}
} else {
if (rw.dir == 1) {/*read */
rc =
meta_indirect_read(drv, rw.addr,
&rw.value);
if (rc)
return rc;
rc = copy_to_user((void *)param1,
&rw,
sizeof(struct demod_rw));
if (rc)
return -EACCES;
} else {
rc =
meta_indirect_write(drv, rw.addr,
rw.value);
if (rc)
return rc;
}
}
break;
/* Set region base for read/write opesrations */
case DEMOD_IOCTL_SET_REGION:
if (copy_from_user(&sr, (void *)param1,
sizeof(struct demod_set_region)) == 0) {
if (sr.dir == 1)/* read */
drv->read_base = sr.base;
else
drv->write_base = sr.base;
}
else
return -EACCES;
break;
case DEMOD_IOCTL_RESET:
/* Reset DEMOD core. Block VBIF access at Top BCSS */
clk_reset(drv->clks[0], CLK_RESET_ASSERT);
writel_relaxed(1, drv->top_bcss + BCSS_VBIF);
udelay(10);
clk_reset(drv->clks[0], CLK_RESET_DEASSERT);
writel_relaxed(0, drv->top_bcss + BCSS_VBIF);
break;
default:
return 0;
}
return 0;
}
/**
* demod_write() - write to DEMOD memory.
*
* @filp: file pointer.
* @buf: buffer to write to
* @count: bytes to write
* @f_pos: internal offset
*/
static ssize_t demod_write(struct file *filp,
const char *buf, size_t count,
loff_t *f_pos)
{
unsigned int base;
struct demod_data *drv = filp->private_data;
int rc;
rc = demod_verify_access(drv->read_base, count);
if (rc)
return rc;
if ((drv->write_base & TOP8) == BASE_EXT) {
base = (unsigned int)drv->ext_ram +
(drv->write_base & LOW20);
} else {
writel_relaxed((drv->write_base & TOP12)>>20,
drv->bcss_regs+
NIBLE_PTR);
/*
* combined with nibble reg(0xd0004)
* the result is 0x00000b70, which
* points to gram packed view
*/
base = BCSSADDR + ACCESS_OFFS+
(drv->write_base & LOW20);
}
if (copy_from_user((void *)base, (void *)buf, count))
return -EACCES;
return count;
}
/**
* msm_demod_probe() - driver probe function.
*
* @pdev: platform device
*/
static int msm_demod_probe(struct platform_device *pdev)
{
int res;
struct demod_data *drv;
struct resource *mem_demod, *top_bcss;
struct device *dd;
DRVDBG("demod driver init.\n");
drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
return -ENOMEM;
drv->bcss_regs = NULL;
drv->ext_ram = NULL;
drv->dev = &pdev->dev;
platform_set_drvdata(pdev, drv);
drv->ref_counter = 0;
mutex_init(&drv->mutex);
mem_demod = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "msm-demod");
if (!mem_demod) {
DRVERR("Missing DEMOD MEM resource");
return -ENXIO;
}
drv->bcss_regs = devm_request_and_ioremap(&pdev->dev, mem_demod);
if (!drv->bcss_regs) {
DRVERR("ioremap failed");
return -ENXIO;
}
top_bcss = platform_get_resource_byname(pdev,
IORESOURCE_MEM, "top-bcss");
if (!top_bcss) {
DRVERR("Missing TOP BCSS MEM resource");
return -ENXIO;
}
drv->top_bcss = devm_request_and_ioremap(&pdev->dev, top_bcss);
if (!drv->top_bcss) {
DRVERR("ioremap failed");
return -ENXIO;
}
DRVDBG("demod BCSS base = 0x%08X\n", BCSSADDR);
DRVDBG("top BCSS base = 0x%08X\n", (unsigned int)drv->top_bcss);
drv->gdsc = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(drv->gdsc)) {
DRVERR("Failed to get BCSS GDSC\n");
return -ENODEV;
}
drv->node = pdev->dev.of_node;
res = demod_clock_setup(&pdev->dev);
if (res)
return res;
wakeup_source_init(&drv->wakeup_src, dev_name(&pdev->dev));
res = alloc_chrdev_region(&demod_minor, 0, 1, "demod");
if (res) {
DRVDBG("uccp: alloc_chrdev_region failed: %d", res);
return -ENODEV;
}
demod_class = class_create(THIS_MODULE, "demod");
if (IS_ERR(demod_class)) {
res = PTR_ERR(demod_class);
DRVDBG("uccp: Error creating class: %d", res);
goto fail_class_create;
}
/* Create char device */
drv->cdev.owner = THIS_MODULE;
cdev_init(&drv->cdev, &demod_fops);
if (cdev_add(&drv->cdev, demod_minor++, 1) != 0) {
DRVDBG("demod: cdev_add failed");
res = -EACCES;
goto fail_cdev_add;
}
dd = device_create(demod_class, NULL, drv->cdev.dev,
NULL, "demod");
if (IS_ERR(dd)) {
DRVERR("uccp: device_create failed: %i",
(int)PTR_ERR(dd));
res = -EACCES;
goto fail_device_create;
}
return 0;
fail_device_create:
cdev_del(&drv->cdev);
fail_cdev_add:
class_destroy(demod_class);
fail_class_create:
unregister_chrdev_region(demod_minor, 1);
return res;
}
/**
* msm_demod_remove() - driver remove function.
*
* @pdev: platform device
*/
static int msm_demod_remove(struct platform_device *pdev)
{
struct demod_data *drv = dev_get_drvdata(&pdev->dev);
DRVDBG("demod driver remove\n");
mutex_destroy(&drv->mutex);
wakeup_source_trash(&drv->wakeup_src);
cdev_del(&drv->cdev);
device_destroy(demod_class, drv->cdev.dev);
class_destroy(demod_class);
unregister_chrdev_region(demod_minor, 1);
return 0;
}
/* Power Management */
static int demod_runtime_suspend(struct device *dev)
{
return 0;
}
static int demod_runtime_resume(struct device *dev)
{
return 0;
}
static const struct dev_pm_ops demod_dev_pm_ops = {
.runtime_suspend = demod_runtime_suspend,
.runtime_resume = demod_runtime_resume,
};
/* Platform driver information */
static struct of_device_id msm_demod_match_table[] = {
{.compatible = "qti,msm-demod"}
};
static struct platform_driver msm_demod_driver = {
.probe = msm_demod_probe,
.remove = msm_demod_remove,
.driver = {
.name = "msm-demod",
.pm = &demod_dev_pm_ops,
.of_match_table = msm_demod_match_table,
},
};
/**
* demod_module_init() - DEMOD driver module init function.
*
* Return 0 on success, error value otherwise.
*/
static int __init demod_module_init(void)
{
int rc;
rc = platform_driver_register(&msm_demod_driver);
if (rc)
DRVERR("platform_driver_register failed: %d", rc);
return rc;
}
/**
* demod_module_exit() - DEMOD driver module exit function.
*/
static void __exit demod_module_exit(void)
{
platform_driver_unregister(&msm_demod_driver);
}
module_init(demod_module_init);
module_exit(demod_module_exit);
MODULE_DESCRIPTION("DEMOD (Internal demodulator) platform device driver");
MODULE_LICENSE("GPL v2");

View File

@ -223,6 +223,7 @@ header-y += kernelcapi.h
header-y += kexec.h
header-y += keyboard.h
header-y += keyctl.h
header-y += ensigma_uccp330.h
ifneq ($(wildcard $(srctree)/arch/$(SRCARCH)/include/uapi/asm/kvm.h \
$(srctree)/arch/$(SRCARCH)/include/asm/kvm.h),)

View File

@ -0,0 +1,35 @@
#ifndef _DEMOD_H_
#define _DEMOD_H_
#include <linux/ioctl.h>
/*
* demod_rw ioctl() argument to R/W 32 bit value
* from demod intrnal registers
*/
struct demod_rw {
unsigned int addr;
unsigned int value;
int dir;/* 1 -read;0 - write */
};
/*
* demod_set_region ioctl() argument
* sets base for read/write operations
*/
struct demod_set_region {
unsigned int base;
int dir; /* 1 -read;0 - write */
};
/*
* defines for IOCTL functions
* read Documentation/ioctl-number.txt
* some random number to avoid coinciding with other ioctl numbers
*/
#define DEMOD_IOCTL_BASE 0xBB
#define DEMOD_IOCTL_RW \
_IOWR(DEMOD_IOCTL_BASE, 0, struct demod_rw)
#define DEMOD_IOCTL_SET_REGION \
_IOW(DEMOD_IOCTL_BASE, 1, struct demod_set_region)
#define DEMOD_IOCTL_RESET \
_IO(DEMOD_IOCTL_BASE, 2)
#endif /* _DEMOD_H_ */