C Linux USB Driver | Best way to print URB buffer contents - c

I'm new to Linux USB driver development, and I absolutely love it so far! I'm currently creating a driver for an Xbox One controller, and I have a question for you guys. In the below code, you will see that I fill an interrupt IN URB in the open function, and I would like to print the contents of the URB buffer in the xb1_int_in_callback() function. What would be the best way to do this? At the moment, I'm using printk(KERN_INFO "int_in_buffer: %s", dev->int_in_buffer), however I don't see the entire URB buffer contents printed, and get a weird string printed to dmesg.
Sorry if this is a simple question, I'm new to programming and C, so I'm still learning as I go, but I absolutely love it so far!
Code:
static void xb1_int_in_callback(struct urb *int_in_urb) {
struct xb1_controller *dev = int_in_urb->context;
printk(KERN_INFO "xb1_int_in_callback successfully called");
printk(KERN_INFO "int_in_buffer: %s", dev->int_in_buffer);
}
static int xb1_open(struct inode *inode, struct file *file) {
printk(KERN_INFO "open function called..");
struct xb1_controller *dev;
struct usb_interface *interface;
int subminor;
int retval = 0;
subminor = iminor(inode);
interface = usb_find_interface(&xb1_driver, subminor);
if(!interface) {
printk(KERN_INFO "Unable to locate interface in open
function");
retval = -ENODEV;
goto exit;
}
dev = usb_get_intfdata(interface);
if(!dev) {
printk(KERN_INFO "Unable to locate dev structure in open
function");
retval = -ENODEV;
goto exit;
}
usb_fill_int_urb(dev->int_in_urb, dev->udev, usb_rcvintpipe(dev-
>udev, dev->int_in_endpoint->bEndpointAddress),
dev->int_in_buffer, dev->int_in_endpoint-
>wMaxPacketSize, xb1_int_in_callback,
dev, dev->int_in_endpoint->bInterval);
dev->int_in_running = 1;
retval = usb_submit_urb(dev->int_in_urb, GFP_KERNEL);
if(retval) {
printk(KERN_INFO "Unable to submit int_in_urb in open
function");
dev->int_in_running = 0;
goto exit;
}
file->private_data = dev;
exit:
return retval;
}
static int xb1_probe(struct usb_interface *interface, const struct
usb_device_id *id) {
struct usb_device *udev = interface_to_usbdev(interface);
struct xb1_controller *dev = NULL;
struct usb_host_interface *iface_desc;
struct usb_endpoint_descriptor *endpoint;
int i;
int retval = -ENODEV;
if(!udev) {
printk(KERN_INFO "udev is NULL in probe function");
xb1_abort(dev);
return retval;
}
dev = kzalloc(sizeof(struct xb1_controller), GFP_KERNEL);
if(!dev) {
printk(KERN_INFO "Unable to allocate memory for dev in probe
function");
xb1_abort(dev);
return retval;
}
dev->udev = udev;
dev->interface = interface;
iface_desc = interface->cur_altsetting;
for(i=0; i<iface_desc->desc.bNumEndpoints; i++) {
endpoint = &iface_desc->endpoint[i].desc;
if(((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ==
USB_DIR_IN)
&& ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
USB_ENDPOINT_XFER_INT)) {
dev->int_in_endpoint = endpoint;
}
}
if(!dev->int_in_endpoint) {
printk(KERN_INFO "Unable to locate interrupt in endpoint for
interface in probe function");
xb1_abort(dev);
return retval;
}
else {
printk(KERN_INFO "Interrupt in endpoint found!");
}

For printing a small buffers (up to 64 bytes long) use printk format:
Raw buffer as a hex string:
%*ph 00 01 02 ... 3f
%*phC 00:01:02: ... :3f
%*phD 00-01-02- ... -3f
%*phN 000102 ... 3f
For larger buffers use print_hex_dump(). Reference here.

Related

How to invoke platform specific driver in host machine?

I am writing a dummy PHY driver as a LKM. This is a platform driver
and I can't call the driver probe since it depends on the compatible string.
How can I make my driver invoke probe in my host machine, which is running
Ubuntu 18.04, kernel version - 5.3.0-40-generic. My machine is X86. I am installing this phy driver on my X86 machine.
Here is my code:
static int phy_platform_probe(struct platform_device *pdev) {
struct custom_port_phy_platform_local *lp;
int ret;
printk(KERN_ALERT "abc..phy_platform_probe..Invoked\r\n");
lp = devm_kzalloc(&pdev->dev,sizeof(struct custom_port_phy_platform_local),GFP_KERNEL);
if (!lp) {
dev_err(&(pdev->dev),"Failed to allocatate platform_local\n");
return -ENOMEM;
}
platform_set_drvdata(pdev, lp);
lp->dev = &pdev->dev;
ret = custom_port_phy_mdio_setup(lp, pdev->dev.of_node);
if (ret < 0) {
dev_err(&(pdev->dev),"Failed to setup MDIO bus\n");
return ret;
}
return 0;
}
static int phy_platform_remove(struct platform_device *pdev) {
struct custom_port_phy_platform_local *lp = platform_get_drvdata(pdev);
dev_info(&pdev->dev,"%s Enter\n",__func__);
custom_port_phy_mdio_teardown(lp);
return 0;
}
static struct phy_driver custom_phy_driver = {
.phy_id = 0x00000002,
.phy_id_mask = 0xffffffff,
.name = "custom_port_phy",
};
static struct platform_driver custom_phy_platform_driver = {
.probe = phy_platform_probe,
.remove = phy_platform_remove,
#if 1
.driver = {
.name = "custom_port_phy"
.of_match_table = port_phy_of_match,
}
#endif
};
#if 1
/* Match table for of_platform binding */
static struct of_device_id port_phy_of_match[] = {
{ .compatible = "custom,custom-port-phy", },
{},
};
#endif
static int __init phy_init(void)
{
int ret = 0;
printk(KERN_ALERT "abc >>> phy_driver_register started\r\n");
ret = phy_driver_register(&custom_phy_driver, THIS_MODULE);
printk(KERN_ALERT "abc >>> phy_driver_register END\r\n");
if(ret < 0) {
printk(KERN_ALERT "custom phy driver registration failed\r\n");
return ret;
}
ret = platform_driver_register(&custom_phy_platform_driver);
if(ret < 0) {
phy_driver_unregister(&custom_phy_driver);
printk("%s: Failed to register as Platform Driver\r\n", __func__);
return ret;
}
return ret;
}
static void __exit phy_exit(void)
{
pr_info("Goodbye phy_driver .\n");
phy_driver_unregister(&custom_phy_driver);
platform_driver_unregister(&custom_phy_platform_driver);
}
module_init(phy_init);
module_exit(phy_exit);
MODULE_LICENSE("GPL");
My code doesn't do probe since it can't find compatible flag.
How can I make it work on my host machine? I know I need dtb file. Even if
I have dtb file, how do I supply it and how to write it that way?

How to support properly kernel suspend/resume feature in kernel driver?

I have a kernel driver with char device :
static const struct file_operations xxxxx_fops = {
.owner = THIS_MODULE,
.read = xxxxx_read,
};
A read callback on this char device :
static int XXXX_read(struct file *filp, char __user *data, size_t len, loff_t *ppos)
{
int err = 0;
int retry = 0;
do {
if (retry == 5) return -3;
err = down_interruptible(&priv.XXXX_sem);
if (err == 0) {
retry = 0;
break;
} else {
retry++;
continue;
}
} while (1);
err = copy_to_user(data, &priv.XXXX_queue[priv.XXXX_read_offset], XXXX_TOTAL_SIZE);
priv.XXXXX_read_offset += XXXXX_TOTAL_SIZE;
if (priv.XXXX_read_offset == XXXX_QUEUE_SIZE) priv.XXXX_read_offset = 0;
if (err) printk ("Error during XXXX copy\n");
return (XXXX_TOTAL_SIZE - err);
};
In my userland code, I open the char device and use read call in a loop :
(...)
while (1)
{
err = read (fd, &ctx->xxxx_queue[xxxxx_offset], XXXX_TOTAL);
if (err != XXXX_TOTAL)
{
PRINT_WARN ("XXXX read byte : %d (errno : %d)", err, errno);
continue;
}
(...)
}
(...)
Everything is running fine.
When I used suspend feature from the kernel by calling :
echo mem > /sys/power/state, an issue occured on resume :
XXXX read byte : -1 (errno : 3)
At this moment most of the time my driver read callback is block
in the semaphore wait (down_interruptible)
The read return -1 on kernel resume. Is there a way to avoid this ?
I tried to looking for a suspend/resume callback to declare on
struct file_operations but nothing.
Do I have just to ignore -3 error fro ioctl ?

Data bus error when using ioread32 in PCIE driver

I am developing a PCIE device driver for Openwrt, which is also a linux system. Here is a weird situation. After Initializing the driver in probe function, I can read(by ioread32) correct data (preset value:123456) from the buffer address obtained from ioremap_nocache. Then when I try to read it every 1 second in periodic timer interrupt handler, the function ioread32 will crash and the serial console presents a Data bus error. Below are code details.
// content of function my_driver_request_mem, this function is called in probe function
int my_driver_request_mem(struct gps_time *gt) {
u32 start, len;
int ret;
int bar = 0;
u32 flags;
ret = pcim_iomap_regions(gt->pdev, BIT(0), "My Driver");
if (ret) {
gt_log("Fail to request IO mem: err: %d\n", ret);
return ret;
}
// gt is a custom struct, and gt->pdev is the pci_dev struct
// obtained from probe function
start = pci_resource_start(gt->pdev, bar);
len = pci_resource_len(gt->pdev, bar);
flags = pci_resource_flags(gt->pdev, bar);
printk(KERN_ALERT "region start: 0x%x, len: %u\n", start, len);
printk(KERN_ALERT "region flags: 0x%x\n", flags);
gt->buffer = ioremap_nocache(start, len);
gt->buffer_len = len;
gt->buffer_start = start;
return 0;
}
Afte the function above is invoked, I read data through gt->buffer:
u32 d = 0;
d = ioread32(gt->buffer); // this operation does not cause fatal error
printk(KERN_ALERT "initial value is: %u", d);
By reading the console output, the ioread32 here is successful, and the right value 123456 is printed. Then I start a timer to read data multiple times
setup_timer(&gt->g_timer, _gps_timer_handler, gt);
mod_timer(&gt->g_timer, jiffies + msecs_to_jiffies(20000));
printk(KERN_ALERT "GPS_TIME: timer created.\n");
The handler function is quit simple:
void _gps_timer_handler(unsigned long data) {
struct gps_time *gt = (struct gps_time*)data;
u32 d;
d = ioread32(gt->buffer); // fatal error in this line
printk(KERN_ALERT "Value: %u\n", d);
mod_timer(&gt->g_timer, jiffies + msecs_to_jiffies(1000));
}
The ioread32 here will cause a fatal error here, and the error info is:
Data bus error, epc == 82db8030, ra == 8009a000
Oops[#1]
CPU: 0 PID: 853 Comm: dropbearkey Tainted: G W 4.4.14 #2
task: 82dd1900 ti:8209a000 task.ti: 8209a000
...
(bunch of numbers)
...
Status: 1100d403 KERNEL EXL IE
Cause : 8080001c (ExcCode 07)
PrId: 0001974c (MIPS 74Kc)
...
First I though this is because IO process should not be done in interrupt context, so I put ioread32 in a tasklet, and invoke that tasklet by tasklet_schedule, but it still fails.
======== Update
A. Below are my ->probe function:
static int gps_time_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret = 0;
struct gps_time *gt;
u8 tmp = 0;
u32 bar_val = 0;
u8 csz = 0;
unsigned long start, end, len, flag;
int bar = 0;
if (gps_time_global_time) {
printk(KERN_ALERT "GPS_TIME: more than one device detected\n");
return -1;
}
gt = gps_time_alloc();
if (gt == NULL) {
printk(KERN_WARNING "GPS_TIME: out of memory\n");
return -ENOMEM;
}
gt->pdev = pdev;
gt->irq = pdev->irq;
ret = pcim_enable_device(pdev);
if (ret) {
printk(KERN_ALERT "GPS_TIME: Fail to enable device %d\n", ret);
goto err;
}
ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
if (ret) {
printk(KERN_WARNING "GPS_TIME: 32-bit DMA not available\n");
return ret;
}
ret = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
if (ret) {
printk(KERN_WARNING "GPS_TIME: 32-bit DMA consistent DMA enable failed\n");
return ret;
}
my_driver_request_mem(gt);
ret = pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &tmp);
if (ret) {
printk(KERN_ALERT "GPS_TIME: Fail to read cache line size\n");
goto err;
}
if (tmp == 0) {
printk(KERN_ALERT "GPS_TIME: Write pci cache line size\n");
pci_write_config_byte(
pdev, PCI_CACHE_LINE_SIZE, L1_CACHE_BYTES / sizeof(u32));
}
pci_write_config_byte(pdev, PCI_LATENCY_TIMER, 0xa8);
pci_set_master(pdev);
pci_set_drvdata(pdev, gt);
// This function is very simple. I just create timer here. The first ioread32 is also included in this function.
gps_time_init_device(gt);
ret = request_irq(pdev->irq, gps_time_isq, IRQF_SHARED, "gps_time", gt);
if (ret) {
printk(KERN_ALERT "GPS_TIME: Fail to request IRQ: %d", ret);
goto err_irq;
}
return 0;
err_region:
pci_release_regions(pdev);
err_irq:
err:
gps_time_free(gt);
return ret;
}
B. More info about the device:
This device is a self-designed chip with PCIE interface. It is built around a Altera Cyclone IV FPGA. The firmware in the chip does nothing except writing constant 123456 into its memory.

Handling multiple device files having same major number but unique minor number

I am new to Linux Kernel Module Programming and have written a dummy character device driver to read and write to a dummy device (it is actually a sample program given in its documentation). The program runs fine when I try it with only one device file, the problem arises when I create a second dummy device file with same major number but different minor number (in sequence i.e, 1). When I write to one file (say devfile0, major : 250, minor : 0) the data is also written to the other file (say devfile1, major : 250, minor : 1) but I just want to write to devfile0 and not to devfile1. Is it possible? What am I possibly doing wrong?
Here is the kernel module I have created:
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/semaphore.h>
#include<asm/uaccess.h>
#include<linux/kmod.h>
struct cdev *newDev;
int maj_no;
int ret;
dev_t crdev;
#define DEVICE_NAME "CryptoDevCHARDEVDRVR"
struct dummy{
char string[100];
int length;
struct semaphore sem;
}device;
int device_open(struct inode *node,struct file *fp)
{
printk(KERN_ALERT "Atempting to open device file\n");
if(down_interruptible(&device.sem) != 0)
{
printk(KERN_ALERT "%s : Unable to Lock file while open\n",DEVICE_NAME);
return -1;
}
printk(KERN_ALERT "File open operation Complete\n");
return 0;
}
ssize_t device_read(struct file* fp,char* buffer, size_t bufsize, loff_t* buffoff)
{
printk(KERN_ALERT "Reading from the device...\n");
ret = copy_to_user(buffer,device.string,bufsize);
device.length = bufsize;
return ret;
}
ssize_t device_write(struct file* fp,const char* buffer, size_t bufsize, loff_t* buffoff)
{
printk(KERN_ALERT "Writing to the device...\n");
ret = copy_from_user(device.string,buffer,bufsize);
printk(KERN_ALERT "%s\n",device.string);
printk(KERN_ALERT "Written\n");
device.length = bufsize;
return ret;
}
int device_close(struct inode* node, struct file* fp)
{
printk(KERN_ALERT "Closing Device File");
up(&device.sem);
printk(KERN_ALERT "Device Close Successfully");
return 0;
}
struct file_operations fop = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_close,
.read = device_read,
.write = device_write
};
static int hello_init(void)
{
ret = alloc_chrdev_region(&crdev,0,50,DEVICE_NAME);
if(ret < 0)
{
printk(KERN_ALERT "\n%s : Unable to assign Character Device Driver Region",DEVICE_NAME);
return ret;
}
maj_no = MAJOR(crdev);
printk(KERN_ALERT "%s : Major Number:%d\n",DEVICE_NAME,maj_no);
newDev = cdev_alloc();
newDev->ops = &fop;
newDev->owner = THIS_MODULE;
ret = cdev_add(newDev,crdev,50);
if(ret < 0)
{
printk(KERN_ALERT "%s : Unable to Register Device Driver\n",DEVICE_NAME);
}
sema_init(&device.sem,1);
printk(KERN_ALERT "Successfully Initialised Device Driver\n");
printk(KERN_ALERT "Test caller");
return 0;
}
static void hello_destroy(void)
{
printk(KERN_ALERT "Killing Hello-Start.c ... Byeeee\n");
cdev_del(newDev);
unregister_chrdev_region(crdev,50);
printk(KERN_ALERT "%s : Successfully Unregistered Driver\n",DEVICE_NAME);
printk(KERN_ALERT "Done");
}
module_init(hello_init);
module_exit(hello_destroy);
Userspace application uses open syscall to open devfile0 and write syscall to write to devfile0.
Here is the code for that:
fp = open(DEVICE , O_RDWR);
if(fp == -1)
{
printf("%s cann't be accessed right now try after sometime\n",DEVICE);
exit(-1);
}
printf("Enter any Character String to transmit to Device:");
fgets(buff,100,stdin);
write(fp,buff,sizeof(buff));
printf("\nWritten: %s\n",buff);
The major number tells you which driver is used to access the
hardware. Each driver is assigned a unique major number; all device
files with the same major number are controlled by the same driver.
All the above major numbers are 3, because they're all controlled by
the same driver.
The minor number is used by the driver to distinguish between the
various hardware it controls. Returning to the example above, although
all three devices are handled by the same driver they have unique
minor numbers because the driver sees them as being different pieces
of hardware.
from The Linux Kernel Module Programming Guide,3.1.6.1. Major and Minor Numbers

ALSA Sound card driver can't call snd_card_free(..) and amixer does not see the control

I'm trying to develop a sound card driver with ALSA.
My goal eventually is to use:
PCM through I2S.
Control the volume through SPI.
For simplicity I started with volume control ONLY, no PCM.
I wrote the driver and compiled but I'm having 2 issues:
I cannot successfully call snd_card_free in the module exit. I tried storing the snd_card pointer which is successfully allocated in the module init in my custom "snd_adsp21479_chip" structure and use container_of to get the snd_card but the card is always null in the module_exit (it was not null in the module_init and is successfully allocated, I have the checks, I could also successfully call snd_card_free(card) in the module init function, but nowhere else). So every time I reload the module to the kernel, I end up with one more sound card and a new control in /dev/snd/ and proc/asound/devices
My biggest problem now is after loading the module I do see a new control (control1) in /dev/snd/ and a new card there, however using amixer I do not see the card's Volume control. I run amixer conctrols, and I do not see my new control.
Here's the code:
#include <linux/wait.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <linux/spi/spi.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#define LOW_SPEED_SPIDEV_SPI_BUS 0
#define LOW_SPEED_SPIDEV_SPI_CS 0
#define LOW_SPEED_SPIDEV_MAX_CLK_HZ 100000
#define SOUND_CARD_NAME "adsp21479"
static struct spi_board_info cal_spi_board_info = {
.modalias = "spidev",
.bus_num = LOW_SPEED_SPIDEV_SPI_BUS,
.chip_select = LOW_SPEED_SPIDEV_SPI_CS,
.max_speed_hz = LOW_SPEED_SPIDEV_MAX_CLK_HZ,
};
static struct spi_device *spi = NULL;
struct snd_adsp21479_chip {
struct spi_device *spi;
struct snd_card *card;
};
/*******************************CONTROL METHODS *******************************/
static int snd_adsp21479_stereo_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info * uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 100;
return 0;
}
static int snd_adsp21479_stereo_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return 0;
}
static int snd_adsp21479_stereo_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
return 0;
}
static struct snd_kcontrol_new volume_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Volume",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = snd_adsp21479_stereo_info,
.get = snd_adsp21479_stereo_get,
.put = snd_adsp21479_stereo_put
};
static int snd_adsp21479_init(struct spi_device *spi)
{
struct snd_card * card = NULL;
int return_value;
char id[10] = SOUND_CARD_NAME;
struct snd_adsp21479_chip *chip;
pr_info("snd_adsp21479_init start\n");
return_value = snd_card_new(&spi->dev,-1,id,THIS_MODULE,sizeof(struct snd_adsp21479_chip),&card);
if(return_value<0)
{
pr_err("snd_card_new failed\n");
return return_value;
}
if(!card)
{
pr_err("snd_card_new did not allocate card");
return -ENODEV;
}
pr_info("sound card=%p created successfully",card);
chip = card->private_data;
chip->card = card;
chip->spi = spi;
strcpy(card->mixername,"adsp21479 Mixer");
return_value = snd_ctl_add(card,snd_ctl_new1(&volume_control,chip));
if(return_value<0)
{
pr_err("snd_ctl_add faile adding control\n");
return return_value;
}
pr_info("created a mixer control");
static struct snd_device_ops ops = { NULL};
strcpy(card->driver,SOUND_CARD_NAME);
strcpy(card->shortname,SOUND_CARD_NAME);
strcpy(card->longname,"DAQRI ADSP21479 Sound Driver");
//create an ALSA device component
return_value = snd_device_new(card, SNDRV_DEV_LOWLEVEL,chip,&ops);
if(return_value<0)
{
printk(KERN_ALERT"creating an ALSA device component failed");
return return_value;
}
pr_info("created an ASLA device SNDRV_DEV_LOWLEVEL");
return_value = snd_card_register(card);
if(return_value<0)
{
pr_err("snd_card_register failed\n");
return return_value;
}
if(!card)
{
pr_err("card turned into null\n");
}
pr_info("registered sound card\n");
pr_info("adsp21479_snd_driver loaded successfully\n");
return 0;
// card here is always ok, and if I call snd_card_free(card) here it works.
}
static int __init adsp21479_spi_module_init(void)
{
struct spi_master *master=NULL;
int err;
pr_info("module init\n");
err = -ENODEV;
master = spi_busnum_to_master(LOW_SPEED_SPIDEV_SPI_BUS);
pr_info("master=%p\n", master);
if (!master)
{
pr_err("spi_busnum_to_master failed");
return err;
}
spi = spi_new_device(master, &cal_spi_board_info);
pr_info("spi device =%p\n", spi);
if (!spi)
{
pr_err("spi_new_device failed");
return err;
}
pr_info("spi device registered\n");
err = 0;
return snd_adsp21479_init(spi);
}
static void __exit adsp21479_spi_module_exit(void)
{
struct snd_adsp21479_chip *chip;
pr_info("module exit");
if (spi)
{
chip = container_of(&spi,struct snd_adsp21479_chip,spi);
if(chip)
{
pr_info("chip is not null\n");
if(chip->spi == spi)
{
pr_info("spi match\n");
}
pr_info("card = %p",chip->card);
if(chip->card != NULL)
{
pr_info("freeing sound card=%p\n",chip->card); // I never get here, it's always null, or if I store card in a global variable, I get an exception. And if I store it with drv_set_drvdata(&spi->dev,card) in the init function and try to obtain it here with a dev_get_drvdata(&spi->dev); and try to free the card with snd_card_free I get an exception as well.
snd_card_free(chip->card);
}
}
pr_info("unregistering spi device\n");
spi_unregister_device(spi);
}
}
module_init(adsp21479_spi_module_init);
module_exit(adsp21479_spi_module_exit);
/***************************SPI DRIVER METHODS ****************************/
MODULE_LICENSE("GPL"); ///WE NEED TO CHANGE IT LATER MAYBE
MODULE_DESCRIPTION(" Sound driver for the ADSP-21479");
So to reiterate: No PCM here ,I'm just trying to control the volume through SPI, and my control doesn't show up in amixer, and I can't free my card. I'm using the spidev as my SPI BUS master, and this driver is for the Sound card SPI slave.

Resources