mirror of
https://github.com/patjak/facetimehd.git
synced 2026-04-09 19:10:01 +02:00
285 lines
6.5 KiB
C
285 lines
6.5 KiB
C
/*
|
|
* Broadcom PCIe 1570 webcam driver
|
|
*
|
|
* Copyright (C) 2014 Patrik Jakobsson (patrik.r.jakobsson@gmail.com)
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation.
|
|
*
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/acpi.h>
|
|
#include <linux/firmware.h>
|
|
#include "bcwc_drv.h"
|
|
#include "bcwc_hw.h"
|
|
#include "bcwc_reg.h"
|
|
#include "isp.h"
|
|
|
|
int isp_mem_init(struct bcwc_private *dev_priv)
|
|
{
|
|
dev_priv->mem = &dev_priv->pdev->resource[BCWC_PCI_S2_MEM];
|
|
if (!dev_priv->mem)
|
|
return -EIO;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct isp_mem_obj *isp_mem_create(struct bcwc_private *dev_priv,
|
|
unsigned int type, resource_size_t size)
|
|
{
|
|
struct isp_mem_obj *obj;
|
|
struct resource *root = dev_priv->mem;
|
|
int ret;
|
|
|
|
obj = kzalloc(sizeof(struct isp_mem_obj), GFP_KERNEL);
|
|
if (!obj)
|
|
return NULL;
|
|
|
|
obj->type = type;
|
|
obj->base.name = "S2 ISP";
|
|
ret = allocate_resource(root, &obj->base, size, root->start, root->end,
|
|
PAGE_SIZE, NULL, NULL);
|
|
if (ret) {
|
|
dev_err(&dev_priv->pdev->dev,
|
|
"Failed to allocate resource (size: %Ld, start: %Ld, end: %Ld)\n",
|
|
size, root->start, root->end);
|
|
kfree(obj);
|
|
obj = NULL;
|
|
}
|
|
|
|
obj->offset = obj->base.start - root->start;
|
|
obj->size = size;
|
|
obj->size_aligned = obj->base.end - obj->base.start;
|
|
|
|
return obj;
|
|
}
|
|
|
|
int isp_mem_destroy(struct isp_mem_obj *obj)
|
|
{
|
|
if (obj) {
|
|
release_resource(&obj->base);
|
|
kfree(obj);
|
|
obj = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int isp_acpi_set_power(struct bcwc_private *dev_priv, int power)
|
|
{
|
|
acpi_status status;
|
|
acpi_handle handle;
|
|
struct acpi_object_list arg_list;
|
|
struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
|
|
union acpi_object args[1];
|
|
union acpi_object *result;
|
|
int ret = 0;
|
|
|
|
status = acpi_get_handle(NULL, "\\_SB.PCI0.RP02.CMRA.CMPE", &handle);
|
|
if (ACPI_FAILURE(status)) {
|
|
dev_err(&dev_priv->pdev->dev,
|
|
"Failed to get S2 CMPE ACPI handle\n");
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
args[0].type = ACPI_TYPE_INTEGER;
|
|
args[0].integer.value = power;
|
|
|
|
arg_list.count = 1;
|
|
arg_list.pointer = args;
|
|
|
|
status = acpi_evaluate_object(handle, NULL, &arg_list, &buffer);
|
|
if (ACPI_FAILURE(status)) {
|
|
dev_err(&dev_priv->pdev->dev,
|
|
"Failed to execute S2 CMPE ACPI method\n");
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
result = buffer.pointer;
|
|
|
|
if (result->type != ACPI_TYPE_INTEGER || result->integer.value != 0) {
|
|
dev_err(&dev_priv->pdev->dev,
|
|
"Invalid ACPI response (len: %Ld)\n", buffer.length);
|
|
ret = -EINVAL;
|
|
}
|
|
|
|
out:
|
|
kfree(buffer.pointer);
|
|
return ret;
|
|
}
|
|
|
|
static int isp_enable_sensor(struct bcwc_private *dev_priv)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int isp_load_firmware(struct bcwc_private *dev_priv)
|
|
{
|
|
const struct firmware *fw;
|
|
struct resource *res;
|
|
int ret = 0;
|
|
|
|
ret = request_firmware(&fw, "fthd.bin", &dev_priv->pdev->dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/*
|
|
* The firmware must be the first thing we allocate since it needs to
|
|
* be at offset 0. Would be better if we preallocated memory for it and
|
|
* then resized it to not waste any space.
|
|
*/
|
|
dev_priv->firmware = isp_mem_create(dev_priv, FTHD_MEM_FIRMWARE,
|
|
fw->size);
|
|
if (!dev_priv->firmware)
|
|
return -ENOMEM;
|
|
|
|
if (res->start != dev_priv->mem->start) {
|
|
dev_err(&dev_priv->pdev->dev,
|
|
"Misaligned firmware memory object (offset: %Ld)\n",
|
|
res->start - dev_priv->mem->start);
|
|
isp_mem_destroy(dev_priv->firmware);
|
|
return -EBUSY;
|
|
}
|
|
|
|
memcpy(dev_priv->s2_mem + dev_priv->firmware->offset, fw->data,
|
|
fw->size);
|
|
|
|
dev_info(&dev_priv->pdev->dev, "Loaded firmware, size: %lukb (%Lukb)\n",
|
|
fw->size / 1024, (res->end - res->start) / 1024);
|
|
|
|
release_firmware(fw);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int isp_init(struct bcwc_private *dev_priv)
|
|
{
|
|
u32 num_channels, queue_size;
|
|
u32 reg;
|
|
int i, retries, ret;
|
|
|
|
ret = isp_mem_init(dev_priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = isp_load_firmware(dev_priv);
|
|
if (ret)
|
|
return ret;
|
|
|
|
isp_acpi_set_power(dev_priv, 1);
|
|
mdelay(20);
|
|
|
|
/* OSX driver configures the PCI bus here but we ignore it */
|
|
|
|
pci_set_power_state(dev_priv->pdev, PCI_D0);
|
|
|
|
isp_enable_sensor(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_IPC_NUM_CHAN);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_IPC_QUEUE_SIZE);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_REG_C3008);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_FW_HEAP_SIZE);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_REG_C3010);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_REG_C3014);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_REG_C3018);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0, ISP_REG_C301C);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0xffffffff, ISP_REG_41024);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
/*
|
|
* Probably the IPC queue
|
|
* FIXME: Check if we can do 64bit writes on PCIe
|
|
*/
|
|
for (i = ISP_IPC_CHAN_START; i <= ISP_IPC_CHAN_END; i += 8) {
|
|
BCWC_ISP_REG_WRITE(0xffffffff, i);
|
|
BCWC_ISP_REG_WRITE(0, i + 4);
|
|
}
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE( 0x80000000, ISP_REG_40008);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
BCWC_ISP_REG_WRITE(0x1, ISP_REG_40004);
|
|
bcwc_hw_pci_post(dev_priv);
|
|
|
|
for (retries = 0; retries < 1000; retries++) {
|
|
reg = BCWC_ISP_REG_READ(ISP_REG_41000);
|
|
if ((reg & 0xf0) > 0)
|
|
break;
|
|
mdelay(10);
|
|
}
|
|
|
|
if (retries >= 1000) {
|
|
dev_info(&dev_priv->pdev->dev, "Init failed! No wake signal\n");
|
|
return -EIO;
|
|
}
|
|
|
|
dev_info(&dev_priv->pdev->dev, "ISP woke up after %dms\n",
|
|
(retries - 1) * 10);
|
|
|
|
BCWC_ISP_REG_WRITE(0xffffffff, ISP_REG_41024);
|
|
|
|
num_channels = BCWC_ISP_REG_READ(ISP_IPC_NUM_CHAN) + 1;
|
|
queue_size = BCWC_ISP_REG_READ(ISP_IPC_QUEUE_SIZE);
|
|
|
|
dev_info(&dev_priv->pdev->dev,
|
|
"Number of IPC channels: %u, queue size: %u\n",
|
|
num_channels, queue_size);
|
|
|
|
if (num_channels > 32) {
|
|
dev_info(&dev_priv->pdev->dev, "Too many IPC channels: %u\n",
|
|
num_channels);
|
|
return -EIO;
|
|
}
|
|
|
|
/*
|
|
bcwc_alloc_dev_mem(queue_size, &ret, 0);
|
|
*/
|
|
|
|
/* Firmware must fit in 4194304 bytes */
|
|
reg = BCWC_ISP_REG_READ(ISP_FW_HEAP_SIZE);
|
|
if (reg > 0x400000) {
|
|
dev_info(&dev_priv->pdev->dev,
|
|
"Firmware request size too big (%u bytes)\n",
|
|
reg);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
dev_info(&dev_priv->pdev->dev, "Firmware request size: %u\n", reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int isp_uninit(struct bcwc_private *dev_priv)
|
|
{
|
|
return 0;
|
|
}
|