From 354e57f3a0a26120af3bcd6c92c355ad00a057c1 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Thu, 7 Nov 2013 10:25:55 +0100 Subject: ARM/serial: at91: switch atmel serial to use gpiolib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This passes the errata fix using a GPIO to control the RTS pin on one of the AT91 chips to use gpiolib instead of the AT91-specific interfaces. Also remove the reliance on compile-time #defines and the cpu_* check and rely on the platform passing down the proper GPIO pin through platform data. This is a prerequisite for getting rid of the local GPIO implementation in the AT91 platform and move toward multiplatform. The patch also adds device tree support for getting the RTS GPIO pin from the device tree on DT boot paths. Signed-off-by: Nicolas Ferre Acked-by: Jean-Christophe PLAGNIOL-VILLARD Signed-off-by: Linus Walleij Acked-by: Greg Kroah-Hartman Signed-off-by: Uwe Kleine-König --- include/linux/platform_data/atmel.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/atmel.h b/include/linux/platform_data/atmel.h index cea9f70133c5..e26b0c14edea 100644 --- a/include/linux/platform_data/atmel.h +++ b/include/linux/platform_data/atmel.h @@ -84,6 +84,7 @@ struct atmel_uart_data { short use_dma_rx; /* use receive DMA? */ void __iomem *regs; /* virt. base address, if any */ struct serial_rs485 rs485; /* rs485 settings */ + int rts_gpio; /* optional RTS GPIO */ }; /* Touchscreen Controller */ -- cgit v1.2.3 From d1debafc381cb1fa340b5d0dc79637ad1d523770 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 3 Feb 2014 14:51:51 +0200 Subject: ASoC: davinci-mcasp: Rename platform data struct Rename the struct for the platform data: snd_platform_data -> davinci_mcasp_pdata Since we have users under arch/arm/mach-davinci/ for this struct add temporary define to avoid breakage. The arch code can be updated later to use the new struct name. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- include/linux/platform_data/davinci_asp.h | 4 +++- sound/soc/davinci/davinci-mcasp.c | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'include/linux/platform_data') diff --git a/include/linux/platform_data/davinci_asp.h b/include/linux/platform_data/davinci_asp.h index 5245992b0367..85ad68f9206a 100644 --- a/include/linux/platform_data/davinci_asp.h +++ b/include/linux/platform_data/davinci_asp.h @@ -18,7 +18,7 @@ #include -struct snd_platform_data { +struct davinci_mcasp_pdata { u32 tx_dma_offset; u32 rx_dma_offset; int asp_chan_q; /* event queue number for ASP channel */ @@ -87,6 +87,8 @@ struct snd_platform_data { int tx_dma_channel; int rx_dma_channel; }; +/* TODO: Fix arch/arm/mach-davinci/ users and remove this define */ +#define snd_platform_data davinci_mcasp_pdata enum { MCASP_VERSION_1 = 0, /* DM646x */ diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 63b1ecc97cb1..e5fce2ed4dc6 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -823,28 +823,28 @@ static const struct snd_soc_component_driver davinci_mcasp_component = { }; /* Some HW specific values and defaults. The rest is filled in from DT. */ -static struct snd_platform_data dm646x_mcasp_pdata = { +static struct davinci_mcasp_pdata dm646x_mcasp_pdata = { .tx_dma_offset = 0x400, .rx_dma_offset = 0x400, .asp_chan_q = EVENTQ_0, .version = MCASP_VERSION_1, }; -static struct snd_platform_data da830_mcasp_pdata = { +static struct davinci_mcasp_pdata da830_mcasp_pdata = { .tx_dma_offset = 0x2000, .rx_dma_offset = 0x2000, .asp_chan_q = EVENTQ_0, .version = MCASP_VERSION_2, }; -static struct snd_platform_data am33xx_mcasp_pdata = { +static struct davinci_mcasp_pdata am33xx_mcasp_pdata = { .tx_dma_offset = 0, .rx_dma_offset = 0, .asp_chan_q = EVENTQ_0, .version = MCASP_VERSION_3, }; -static struct snd_platform_data dra7_mcasp_pdata = { +static struct davinci_mcasp_pdata dra7_mcasp_pdata = { .tx_dma_offset = 0x200, .rx_dma_offset = 0x284, .asp_chan_q = EVENTQ_0, @@ -912,11 +912,11 @@ err1: return ret; } -static struct snd_platform_data *davinci_mcasp_set_pdata_from_of( +static struct davinci_mcasp_pdata *davinci_mcasp_set_pdata_from_of( struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; - struct snd_platform_data *pdata = NULL; + struct davinci_mcasp_pdata *pdata = NULL; const struct of_device_id *match = of_match_device(mcasp_dt_ids, &pdev->dev); struct of_phandle_args dma_spec; @@ -929,7 +929,7 @@ static struct snd_platform_data *davinci_mcasp_set_pdata_from_of( pdata = pdev->dev.platform_data; return pdata; } else if (match) { - pdata = (struct snd_platform_data *) match->data; + pdata = (struct davinci_mcasp_pdata*) match->data; } else { /* control shouldn't reach here. something is wrong */ ret = -EINVAL; @@ -1023,7 +1023,7 @@ static int davinci_mcasp_probe(struct platform_device *pdev) { struct davinci_pcm_dma_params *dma_data; struct resource *mem, *ioarea, *res, *dat; - struct snd_platform_data *pdata; + struct davinci_mcasp_pdata *pdata; struct davinci_mcasp *mcasp; int ret; -- cgit v1.2.3 From 91eef3e2fee581b00f027bbb0d144788a3c609a9 Mon Sep 17 00:00:00 2001 From: Pavel Machek Date: Tue, 21 Jan 2014 21:56:27 +0100 Subject: staging/bluetooth: Add hci_h4p driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add hci_h4p bluetooth driver to staging tree. This device is used for example on Nokia N900 cell phone. Signed-off-by: Pali Rohár Signed-off-by: Pavel Machek Thanks-to: Sebastian Reichel Thanks-to: Joe Perches Signed-off-by: Greg Kroah-Hartman --- drivers/staging/Kconfig | 2 + drivers/staging/Makefile | 1 + drivers/staging/nokia_h4p/Kconfig | 9 + drivers/staging/nokia_h4p/Makefile | 6 + drivers/staging/nokia_h4p/TODO | 140 ++++ drivers/staging/nokia_h4p/hci_h4p.h | 228 +++++ drivers/staging/nokia_h4p/nokia_core.c | 1205 +++++++++++++++++++++++++++ drivers/staging/nokia_h4p/nokia_fw-bcm.c | 147 ++++ drivers/staging/nokia_h4p/nokia_fw-csr.c | 150 ++++ drivers/staging/nokia_h4p/nokia_fw-ti1273.c | 110 +++ drivers/staging/nokia_h4p/nokia_fw.c | 195 +++++ drivers/staging/nokia_h4p/nokia_uart.c | 199 +++++ include/linux/platform_data/bt-nokia-h4p.h | 38 + 13 files changed, 2430 insertions(+) create mode 100644 drivers/staging/nokia_h4p/Kconfig create mode 100644 drivers/staging/nokia_h4p/Makefile create mode 100644 drivers/staging/nokia_h4p/TODO create mode 100644 drivers/staging/nokia_h4p/hci_h4p.h create mode 100644 drivers/staging/nokia_h4p/nokia_core.c create mode 100644 drivers/staging/nokia_h4p/nokia_fw-bcm.c create mode 100644 drivers/staging/nokia_h4p/nokia_fw-csr.c create mode 100644 drivers/staging/nokia_h4p/nokia_fw-ti1273.c create mode 100644 drivers/staging/nokia_h4p/nokia_fw.c create mode 100644 drivers/staging/nokia_h4p/nokia_uart.c create mode 100644 include/linux/platform_data/bt-nokia-h4p.h (limited to 'include/linux/platform_data') diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 93a7ecf936ac..babb1cf67ce7 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -148,4 +148,6 @@ source "drivers/staging/dgap/Kconfig" source "drivers/staging/gs_fpgaboot/Kconfig" +source "drivers/staging/nokia_h4p/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index 399f9fe07014..2c4949a9bd9b 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -66,3 +66,4 @@ obj-$(CONFIG_DGNC) += dgnc/ obj-$(CONFIG_DGAP) += dgap/ obj-$(CONFIG_MTD_SPINAND_MT29F) += mt29f_spinand/ obj-$(CONFIG_GS_FPGABOOT) += gs_fpgaboot/ +obj-$(CONFIG_BT_NOKIA_H4P) += nokia_h4p/ diff --git a/drivers/staging/nokia_h4p/Kconfig b/drivers/staging/nokia_h4p/Kconfig new file mode 100644 index 000000000000..4336c0ad065b --- /dev/null +++ b/drivers/staging/nokia_h4p/Kconfig @@ -0,0 +1,9 @@ +config BT_NOKIA_H4P + tristate "HCI driver with H4 Nokia extensions" + depends on BT && ARCH_OMAP + help + Bluetooth HCI driver with H4 extensions. This driver provides + support for H4+ Bluetooth chip with vendor-specific H4 extensions. + + Say Y here to compile support for h4 extended devices into the kernel + or say M to compile it as module (btnokia_h4p). diff --git a/drivers/staging/nokia_h4p/Makefile b/drivers/staging/nokia_h4p/Makefile new file mode 100644 index 000000000000..9625db4a9af3 --- /dev/null +++ b/drivers/staging/nokia_h4p/Makefile @@ -0,0 +1,6 @@ + +obj-$(CONFIG_BT_NOKIA_H4P) += btnokia_h4p.o +btnokia_h4p-objs := nokia_core.o nokia_fw.o nokia_uart.o nokia_fw-csr.o \ + nokia_fw-bcm.o nokia_fw-ti1273.o + +ccflags-y += -D__CHECK_ENDIAN__ diff --git a/drivers/staging/nokia_h4p/TODO b/drivers/staging/nokia_h4p/TODO new file mode 100644 index 000000000000..d997afe14173 --- /dev/null +++ b/drivers/staging/nokia_h4p/TODO @@ -0,0 +1,140 @@ +Few attempts to submission have been made, last review comments were received in + +Date: Wed, 15 Jan 2014 19:01:51 -0800 +From: Marcel Holtmann +Subject: Re: [PATCH v6] Bluetooth: Add hci_h4p driver + +Some code refactoring is still needed. + +TODO: + +> +++ b/drivers/bluetooth/hci_h4p.h + +can we please get the naming straight. File names do not start with +hci_ anymore. We moved away from it since that term is too generic. + +> +#define FW_NAME_TI1271_LE "ti1273_le.bin" +> +#define FW_NAME_TI1271 "ti1273.bin" +> +#define FW_NAME_BCM2048 "bcmfw.bin" +> +#define FW_NAME_CSR "bc4fw.bin" + +We do these have to be global in a header file. This should be +confined to the specific firmware part. + +> +struct hci_h4p_info { + +Can we please get rid of the hci_ prefix for everything. Copying from +drivers that are over 10 years old is not a good idea. Please look at +recent ones. + +> + struct timer_list lazy_release; + +Timer? Not delayed work? + +> +void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val); +> +u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset); +> +void hci_h4p_set_rts(struct hci_h4p_info *info, int active); +> +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, int timeout_ms); +> +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which); +> +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which); +> +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed); +> +int hci_h4p_reset_uart(struct hci_h4p_info *info); +> +void hci_h4p_init_uart(struct hci_h4p_info *info); +> +void hci_h4p_enable_tx(struct hci_h4p_info *info); +> +void hci_h4p_store_regs(struct hci_h4p_info *info); +> +void hci_h4p_restore_regs(struct hci_h4p_info *info); +> +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable); + +These are a lot of public functions. Are they all really needed or can +the code be done smart. + +> +static ssize_t hci_h4p_store_bdaddr(struct device *dev, +> + struct device_attribute *attr, +> + const char *buf, size_t count) +> +{ +> + struct hci_h4p_info *info = dev_get_drvdata(dev); + +Since none of these devices can function without having a valid +address, the way this should work is that we should not register the +HCI device when probing the platform device. + +The HCI device should be registered once a valid address has been +written into the sysfs file. I do not want to play the tricks with +bringing up the device without a valid address. + +> + hdev->close = hci_h4p_hci_close; +> + hdev->flush = hci_h4p_hci_flush; +> + hdev->send = hci_h4p_hci_send_frame; + +It needs to use hdev->setup to load the firmware. I assume the +firmware only needs to be loaded once. That is exactly what +hdev->setup does. It gets executed once. + +> + set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks); + +Is this quirk really needed? Normally only Bluetooth 1.1 and early +devices qualify for it. + +> +static int hci_h4p_bcm_set_bdaddr(struct hci_h4p_info *info, struct sk_buff *skb) +> +{ +> + int i; +> + static const u8 nokia_oui[3] = {0x00, 0x1f, 0xdf}; +> + int not_valid; + +Has this actually been confirmed that we can just randomly set an +address out of the Nokia range. I do not think so. This is a pretty +bad idea. + +I have no interest in merging a driver with such a hack. + +> + not_valid = 1; +> + for (i = 0; i < 6; i++) { +> + if (info->bd_addr[i] != 0x00) { +> + not_valid = 0; +> + break; +> + } +> + } + +Anybody every heard of memcmp or bacmp and BDADDR_ANY? + +> + if (not_valid) { +> + dev_info(info->dev, "Valid bluetooth address not found," +> + " setting some random\n"); +> + /* When address is not valid, use some random */ +> + memcpy(info->bd_addr, nokia_oui, 3); +> + get_random_bytes(info->bd_addr + 3, 3); +> + } + + +And why does every single chip firmware does this differently. Seriously, this is a mess. + +> +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb) +> +{ +> + switch (info->man_id) { +> + case H4P_ID_CSR: +> + hci_h4p_bc4_parse_fw_event(info, skb); +> + break; +... +> +} + +We have proper HCI sync command handling in recent kernels. I really +do not know why this is hand coded these days. Check how the Intel +firmware loading inside btusb.c does it. + +> +inline u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset) +> +{ +> + return __raw_readb(info->uart_base + (offset << 2)); +> +} + +Inline in a *.c file for a non-static function. Makes no sense to me. + +> +/** +> + * struct hci_h4p_platform data - hci_h4p Platform data structure +> + */ +> +struct hci_h4p_platform_data { + +please have a proper name here. For example +btnokia_h4p_platform_data. + +Please send patches to Greg Kroah-Hartman and Cc: +Pavel Machek diff --git a/drivers/staging/nokia_h4p/hci_h4p.h b/drivers/staging/nokia_h4p/hci_h4p.h new file mode 100644 index 000000000000..fd7a6407f20c --- /dev/null +++ b/drivers/staging/nokia_h4p/hci_h4p.h @@ -0,0 +1,228 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2005-2008 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef __DRIVERS_BLUETOOTH_HCI_H4P_H +#define __DRIVERS_BLUETOOTH_HCI_H4P_H + +#include +#include +#include + +#define FW_NAME_TI1271_PRELE "ti1273_prele.bin" +#define FW_NAME_TI1271_LE "ti1273_le.bin" +#define FW_NAME_TI1271 "ti1273.bin" +#define FW_NAME_BCM2048 "bcmfw.bin" +#define FW_NAME_CSR "bc4fw.bin" + +#define UART_SYSC_OMAP_RESET 0x03 +#define UART_SYSS_RESETDONE 0x01 +#define UART_OMAP_SCR_EMPTY_THR 0x08 +#define UART_OMAP_SCR_WAKEUP 0x10 +#define UART_OMAP_SSR_WAKEUP 0x02 +#define UART_OMAP_SSR_TXFULL 0x01 + +#define UART_OMAP_SYSC_IDLEMODE 0x03 +#define UART_OMAP_SYSC_IDLEMASK (3 << UART_OMAP_SYSC_IDLEMODE) + +#define UART_OMAP_SYSC_FORCE_IDLE (0 << UART_OMAP_SYSC_IDLEMODE) +#define UART_OMAP_SYSC_NO_IDLE (1 << UART_OMAP_SYSC_IDLEMODE) +#define UART_OMAP_SYSC_SMART_IDLE (2 << UART_OMAP_SYSC_IDLEMODE) + +#define H4P_TRANSFER_MODE 1 +#define H4P_SCHED_TRANSFER_MODE 2 +#define H4P_ACTIVE_MODE 3 + +struct hci_h4p_info { + struct timer_list lazy_release; + struct hci_dev *hdev; + spinlock_t lock; + + void __iomem *uart_base; + unsigned long uart_phys_base; + int irq; + struct device *dev; + u8 chip_type; + u8 bt_wakeup_gpio; + u8 host_wakeup_gpio; + u8 reset_gpio; + u8 reset_gpio_shared; + u8 bt_sysclk; + u8 man_id; + u8 ver_id; + + struct sk_buff_head fw_queue; + struct sk_buff *alive_cmd_skb; + struct completion init_completion; + struct completion fw_completion; + struct completion test_completion; + int fw_error; + int init_error; + + struct sk_buff_head txq; + + struct sk_buff *rx_skb; + long rx_count; + unsigned long rx_state; + unsigned long garbage_bytes; + + u8 bd_addr[6]; + struct sk_buff_head *fw_q; + + int pm_enabled; + int tx_enabled; + int autorts; + int rx_enabled; + unsigned long pm_flags; + + int tx_clocks_en; + int rx_clocks_en; + spinlock_t clocks_lock; + struct clk *uart_iclk; + struct clk *uart_fclk; + atomic_t clk_users; + u16 dll; + u16 dlh; + u16 ier; + u16 mdr1; + u16 efr; +}; + +struct hci_h4p_radio_hdr { + __u8 evt; + __u8 dlen; +} __attribute__ ((packed)); + +struct hci_h4p_neg_hdr { + __u8 dlen; +} __attribute__ ((packed)); +#define H4P_NEG_HDR_SIZE 1 + +#define H4P_NEG_REQ 0x00 +#define H4P_NEG_ACK 0x20 +#define H4P_NEG_NAK 0x40 + +#define H4P_PROTO_PKT 0x44 +#define H4P_PROTO_BYTE 0x4c + +#define H4P_ID_CSR 0x02 +#define H4P_ID_BCM2048 0x04 +#define H4P_ID_TI1271 0x31 + +struct hci_h4p_neg_cmd { + __u8 ack; + __u16 baud; + __u16 unused1; + __u8 proto; + __u16 sys_clk; + __u16 unused2; +} __attribute__ ((packed)); + +struct hci_h4p_neg_evt { + __u8 ack; + __u16 baud; + __u16 unused1; + __u8 proto; + __u16 sys_clk; + __u16 unused2; + __u8 man_id; + __u8 ver_id; +} __attribute__ ((packed)); + +#define H4P_ALIVE_REQ 0x55 +#define H4P_ALIVE_RESP 0xcc + +struct hci_h4p_alive_hdr { + __u8 dlen; +} __attribute__ ((packed)); +#define H4P_ALIVE_HDR_SIZE 1 + +struct hci_h4p_alive_pkt { + __u8 mid; + __u8 unused; +} __attribute__ ((packed)); + +#define MAX_BAUD_RATE 921600 +#define BC4_MAX_BAUD_RATE 3692300 +#define UART_CLOCK 48000000 +#define BT_INIT_DIVIDER 320 +#define BT_BAUDRATE_DIVIDER 384000000 +#define BT_SYSCLK_DIV 1000 +#define INIT_SPEED 120000 + +#define H4_TYPE_SIZE 1 +#define H4_RADIO_HDR_SIZE 2 + +/* H4+ packet types */ +#define H4_CMD_PKT 0x01 +#define H4_ACL_PKT 0x02 +#define H4_SCO_PKT 0x03 +#define H4_EVT_PKT 0x04 +#define H4_NEG_PKT 0x06 +#define H4_ALIVE_PKT 0x07 +#define H4_RADIO_PKT 0x08 + +/* TX states */ +#define WAIT_FOR_PKT_TYPE 1 +#define WAIT_FOR_HEADER 2 +#define WAIT_FOR_DATA 3 + +struct hci_fw_event { + struct hci_event_hdr hev; + struct hci_ev_cmd_complete cmd; + u8 status; +} __attribute__ ((packed)); + +int hci_h4p_send_alive_packet(struct hci_h4p_info *info); + +void hci_h4p_bcm_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb); +int hci_h4p_bcm_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue); + +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb); +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue); + +void hci_h4p_ti1273_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb); +int hci_h4p_ti1273_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue); + +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue); +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue); +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb); + +void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val); +u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset); +void hci_h4p_set_rts(struct hci_h4p_info *info, int active); +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, int timeout_ms); +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which); +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which); +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed); +int hci_h4p_reset_uart(struct hci_h4p_info *info); +void hci_h4p_init_uart(struct hci_h4p_info *info); +void hci_h4p_enable_tx(struct hci_h4p_info *info); +void hci_h4p_store_regs(struct hci_h4p_info *info); +void hci_h4p_restore_regs(struct hci_h4p_info *info); +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable); + +#endif /* __DRIVERS_BLUETOOTH_HCI_H4P_H */ diff --git a/drivers/staging/nokia_h4p/nokia_core.c b/drivers/staging/nokia_h4p/nokia_core.c new file mode 100644 index 000000000000..5da84b06eff3 --- /dev/null +++ b/drivers/staging/nokia_h4p/nokia_core.c @@ -0,0 +1,1205 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2005-2008 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * Thanks to all the Nokia people that helped with this driver, + * including Ville Tervo and Roger Quadros. + * + * Power saving functionality was removed from this driver to make + * merging easier. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "hci_h4p.h" + +/* This should be used in function that cannot release clocks */ +static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&info->clocks_lock, flags); + if (enable && !*clock) { + BT_DBG("Enabling %p", clock); + clk_prepare_enable(info->uart_fclk); + clk_prepare_enable(info->uart_iclk); + if (atomic_read(&info->clk_users) == 0) + hci_h4p_restore_regs(info); + atomic_inc(&info->clk_users); + } + + if (!enable && *clock) { + BT_DBG("Disabling %p", clock); + if (atomic_dec_and_test(&info->clk_users)) + hci_h4p_store_regs(info); + clk_disable_unprepare(info->uart_fclk); + clk_disable_unprepare(info->uart_iclk); + } + + *clock = enable; + spin_unlock_irqrestore(&info->clocks_lock, flags); +} + +static void hci_h4p_lazy_clock_release(unsigned long data) +{ + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + if (!info->tx_enabled) + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + spin_unlock_irqrestore(&info->lock, flags); +} + +/* Power management functions */ +void hci_h4p_smart_idle(struct hci_h4p_info *info, bool enable) +{ + u8 v; + + v = hci_h4p_inb(info, UART_OMAP_SYSC); + v &= ~(UART_OMAP_SYSC_IDLEMASK); + + if (enable) + v |= UART_OMAP_SYSC_SMART_IDLE; + else + v |= UART_OMAP_SYSC_NO_IDLE; + + hci_h4p_outb(info, UART_OMAP_SYSC, v); +} + +static inline void h4p_schedule_pm(struct hci_h4p_info *info) +{ +} + +static void hci_h4p_disable_tx(struct hci_h4p_info *info) +{ + if (!info->pm_enabled) + return; + + /* Re-enable smart-idle */ + hci_h4p_smart_idle(info, 1); + + gpio_set_value(info->bt_wakeup_gpio, 0); + mod_timer(&info->lazy_release, jiffies + msecs_to_jiffies(100)); + info->tx_enabled = 0; +} + +void hci_h4p_enable_tx(struct hci_h4p_info *info) +{ + unsigned long flags; + + if (!info->pm_enabled) + return; + + h4p_schedule_pm(info); + + spin_lock_irqsave(&info->lock, flags); + del_timer(&info->lazy_release); + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + info->tx_enabled = 1; + gpio_set_value(info->bt_wakeup_gpio, 1); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + /* + * Disable smart-idle as UART TX interrupts + * are not wake-up capable + */ + hci_h4p_smart_idle(info, 0); + + spin_unlock_irqrestore(&info->lock, flags); +} + +static void hci_h4p_disable_rx(struct hci_h4p_info *info) +{ + if (!info->pm_enabled) + return; + + info->rx_enabled = 0; + + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR) + return; + + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT)) + return; + + __hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS); + info->autorts = 0; + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); +} + +static void hci_h4p_enable_rx(struct hci_h4p_info *info) +{ + if (!info->pm_enabled) + return; + + h4p_schedule_pm(info); + + hci_h4p_set_clk(info, &info->rx_clocks_en, 1); + info->rx_enabled = 1; + + if (!(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT)) + return; + + __hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS); + info->autorts = 1; +} + +/* Negotiation functions */ +int hci_h4p_send_alive_packet(struct hci_h4p_info *info) +{ + struct hci_h4p_alive_hdr *hdr; + struct hci_h4p_alive_pkt *pkt; + struct sk_buff *skb; + unsigned long flags; + int len; + + BT_DBG("Sending alive packet"); + + len = H4_TYPE_SIZE + sizeof(*hdr) + sizeof(*pkt); + skb = bt_skb_alloc(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + memset(skb->data, 0x00, len); + *skb_put(skb, 1) = H4_ALIVE_PKT; + hdr = (struct hci_h4p_alive_hdr *)skb_put(skb, sizeof(*hdr)); + hdr->dlen = sizeof(*pkt); + pkt = (struct hci_h4p_alive_pkt *)skb_put(skb, sizeof(*pkt)); + pkt->mid = H4P_ALIVE_REQ; + + skb_queue_tail(&info->txq, skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + + BT_DBG("Alive packet sent"); + + return 0; +} + +static void hci_h4p_alive_packet(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + struct hci_h4p_alive_hdr *hdr; + struct hci_h4p_alive_pkt *pkt; + + BT_DBG("Received alive packet"); + hdr = (struct hci_h4p_alive_hdr *)skb->data; + if (hdr->dlen != sizeof(*pkt)) { + dev_err(info->dev, "Corrupted alive message\n"); + info->init_error = -EIO; + goto finish_alive; + } + + pkt = (struct hci_h4p_alive_pkt *)skb_pull(skb, sizeof(*hdr)); + if (pkt->mid != H4P_ALIVE_RESP) { + dev_err(info->dev, "Could not negotiate hci_h4p settings\n"); + info->init_error = -EINVAL; + } + +finish_alive: + complete(&info->init_completion); + kfree_skb(skb); +} + +static int hci_h4p_send_negotiation(struct hci_h4p_info *info) +{ + struct hci_h4p_neg_cmd *neg_cmd; + struct hci_h4p_neg_hdr *neg_hdr; + struct sk_buff *skb; + unsigned long flags; + int err, len; + u16 sysclk; + + BT_DBG("Sending negotiation.."); + + switch (info->bt_sysclk) { + case 1: + sysclk = 12000; + break; + case 2: + sysclk = 38400; + break; + default: + return -EINVAL; + } + + len = sizeof(*neg_cmd) + sizeof(*neg_hdr) + H4_TYPE_SIZE; + skb = bt_skb_alloc(len, GFP_KERNEL); + if (!skb) + return -ENOMEM; + + memset(skb->data, 0x00, len); + *skb_put(skb, 1) = H4_NEG_PKT; + neg_hdr = (struct hci_h4p_neg_hdr *)skb_put(skb, sizeof(*neg_hdr)); + neg_cmd = (struct hci_h4p_neg_cmd *)skb_put(skb, sizeof(*neg_cmd)); + + neg_hdr->dlen = sizeof(*neg_cmd); + neg_cmd->ack = H4P_NEG_REQ; + neg_cmd->baud = cpu_to_le16(BT_BAUDRATE_DIVIDER/MAX_BAUD_RATE); + neg_cmd->proto = H4P_PROTO_BYTE; + neg_cmd->sys_clk = cpu_to_le16(sysclk); + + hci_h4p_change_speed(info, INIT_SPEED); + + hci_h4p_set_rts(info, 1); + info->init_error = 0; + init_completion(&info->init_completion); + skb_queue_tail(&info->txq, skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + + if (!wait_for_completion_interruptible_timeout(&info->init_completion, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + if (info->init_error < 0) + return info->init_error; + + /* Change to operational settings */ + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS); + hci_h4p_set_rts(info, 0); + hci_h4p_change_speed(info, MAX_BAUD_RATE); + + err = hci_h4p_wait_for_cts(info, 1, 100); + if (err < 0) + return err; + + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS); + init_completion(&info->init_completion); + err = hci_h4p_send_alive_packet(info); + + if (err < 0) + return err; + + if (!wait_for_completion_interruptible_timeout(&info->init_completion, + msecs_to_jiffies(1000))) + return -ETIMEDOUT; + + if (info->init_error < 0) + return info->init_error; + + BT_DBG("Negotiation successful"); + return 0; +} + +static void hci_h4p_negotiation_packet(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + struct hci_h4p_neg_hdr *hdr; + struct hci_h4p_neg_evt *evt; + + hdr = (struct hci_h4p_neg_hdr *)skb->data; + if (hdr->dlen != sizeof(*evt)) { + info->init_error = -EIO; + goto finish_neg; + } + + evt = (struct hci_h4p_neg_evt *)skb_pull(skb, sizeof(*hdr)); + + if (evt->ack != H4P_NEG_ACK) { + dev_err(info->dev, "Could not negotiate hci_h4p settings\n"); + info->init_error = -EINVAL; + } + + info->man_id = evt->man_id; + info->ver_id = evt->ver_id; + +finish_neg: + + complete(&info->init_completion); + kfree_skb(skb); +} + +/* H4 packet handling functions */ +static int hci_h4p_get_hdr_len(struct hci_h4p_info *info, u8 pkt_type) +{ + long retval; + + switch (pkt_type) { + case H4_EVT_PKT: + retval = HCI_EVENT_HDR_SIZE; + break; + case H4_ACL_PKT: + retval = HCI_ACL_HDR_SIZE; + break; + case H4_SCO_PKT: + retval = HCI_SCO_HDR_SIZE; + break; + case H4_NEG_PKT: + retval = H4P_NEG_HDR_SIZE; + break; + case H4_ALIVE_PKT: + retval = H4P_ALIVE_HDR_SIZE; + break; + case H4_RADIO_PKT: + retval = H4_RADIO_HDR_SIZE; + break; + default: + dev_err(info->dev, "Unknown H4 packet type 0x%.2x\n", pkt_type); + retval = -1; + break; + } + + return retval; +} + +static unsigned int hci_h4p_get_data_len(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + long retval = -1; + struct hci_acl_hdr *acl_hdr; + struct hci_sco_hdr *sco_hdr; + struct hci_event_hdr *evt_hdr; + struct hci_h4p_neg_hdr *neg_hdr; + struct hci_h4p_alive_hdr *alive_hdr; + struct hci_h4p_radio_hdr *radio_hdr; + + switch (bt_cb(skb)->pkt_type) { + case H4_EVT_PKT: + evt_hdr = (struct hci_event_hdr *)skb->data; + retval = evt_hdr->plen; + break; + case H4_ACL_PKT: + acl_hdr = (struct hci_acl_hdr *)skb->data; + retval = le16_to_cpu(acl_hdr->dlen); + break; + case H4_SCO_PKT: + sco_hdr = (struct hci_sco_hdr *)skb->data; + retval = sco_hdr->dlen; + break; + case H4_RADIO_PKT: + radio_hdr = (struct hci_h4p_radio_hdr *)skb->data; + retval = radio_hdr->dlen; + break; + case H4_NEG_PKT: + neg_hdr = (struct hci_h4p_neg_hdr *)skb->data; + retval = neg_hdr->dlen; + break; + case H4_ALIVE_PKT: + alive_hdr = (struct hci_h4p_alive_hdr *)skb->data; + retval = alive_hdr->dlen; + break; + } + + return retval; +} + +static inline void hci_h4p_recv_frame(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + if (unlikely(!test_bit(HCI_RUNNING, &info->hdev->flags))) { + switch (bt_cb(skb)->pkt_type) { + case H4_NEG_PKT: + hci_h4p_negotiation_packet(info, skb); + info->rx_state = WAIT_FOR_PKT_TYPE; + return; + case H4_ALIVE_PKT: + hci_h4p_alive_packet(info, skb); + info->rx_state = WAIT_FOR_PKT_TYPE; + return; + } + + if (!test_bit(HCI_UP, &info->hdev->flags)) { + BT_DBG("fw_event"); + hci_h4p_parse_fw_event(info, skb); + return; + } + } + + hci_recv_frame(info->hdev, skb); + BT_DBG("Frame sent to upper layer"); +} + +static inline void hci_h4p_handle_byte(struct hci_h4p_info *info, u8 byte) +{ + switch (info->rx_state) { + case WAIT_FOR_PKT_TYPE: + bt_cb(info->rx_skb)->pkt_type = byte; + info->rx_count = hci_h4p_get_hdr_len(info, byte); + if (info->rx_count < 0) { + info->hdev->stat.err_rx++; + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + } else { + info->rx_state = WAIT_FOR_HEADER; + } + break; + case WAIT_FOR_HEADER: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + if (info->rx_count != 0) + break; + info->rx_count = hci_h4p_get_data_len(info, info->rx_skb); + if (info->rx_count > skb_tailroom(info->rx_skb)) { + dev_err(info->dev, "frame too long\n"); + info->garbage_bytes = info->rx_count + - skb_tailroom(info->rx_skb); + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + break; + } + info->rx_state = WAIT_FOR_DATA; + break; + case WAIT_FOR_DATA: + info->rx_count--; + *skb_put(info->rx_skb, 1) = byte; + break; + default: + WARN_ON(1); + break; + } + + if (info->rx_count == 0) { + /* H4+ devices should always send word aligned packets */ + if (!(info->rx_skb->len % 2)) + info->garbage_bytes++; + hci_h4p_recv_frame(info, info->rx_skb); + info->rx_skb = NULL; + } +} + +static void hci_h4p_rx_tasklet(unsigned long data) +{ + u8 byte; + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + + BT_DBG("tasklet woke up"); + BT_DBG("rx_tasklet woke up"); + + while (hci_h4p_inb(info, UART_LSR) & UART_LSR_DR) { + byte = hci_h4p_inb(info, UART_RX); + if (info->garbage_bytes) { + info->garbage_bytes--; + continue; + } + if (info->rx_skb == NULL) { + info->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, + GFP_ATOMIC | GFP_DMA); + if (!info->rx_skb) { + dev_err(info->dev, + "No memory for new packet\n"); + goto finish_rx; + } + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_skb->dev = (void *)info->hdev; + } + info->hdev->stat.byte_rx++; + hci_h4p_handle_byte(info, byte); + } + + if (!info->rx_enabled) { + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT && + info->autorts) { + __hci_h4p_set_auto_ctsrts(info, 0 , UART_EFR_RTS); + info->autorts = 0; + } + /* Flush posted write to avoid spurious interrupts */ + hci_h4p_inb(info, UART_OMAP_SCR); + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); + } + +finish_rx: + BT_DBG("rx_ended"); +} + +static void hci_h4p_tx_tasklet(unsigned long data) +{ + unsigned int sent = 0; + struct sk_buff *skb; + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + + BT_DBG("tasklet woke up"); + BT_DBG("tx_tasklet woke up"); + + if (info->autorts != info->rx_enabled) { + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) { + if (info->autorts && !info->rx_enabled) { + __hci_h4p_set_auto_ctsrts(info, 0, + UART_EFR_RTS); + info->autorts = 0; + } + if (!info->autorts && info->rx_enabled) { + __hci_h4p_set_auto_ctsrts(info, 1, + UART_EFR_RTS); + info->autorts = 1; + } + } else { + hci_h4p_outb(info, UART_OMAP_SCR, + hci_h4p_inb(info, UART_OMAP_SCR) | + UART_OMAP_SCR_EMPTY_THR); + goto finish_tx; + } + } + + skb = skb_dequeue(&info->txq); + if (!skb) { + /* No data in buffer */ + BT_DBG("skb ready"); + if (hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT) { + hci_h4p_outb(info, UART_IER, + hci_h4p_inb(info, UART_IER) & + ~UART_IER_THRI); + hci_h4p_inb(info, UART_OMAP_SCR); + hci_h4p_disable_tx(info); + return; + } + hci_h4p_outb(info, UART_OMAP_SCR, + hci_h4p_inb(info, UART_OMAP_SCR) | + UART_OMAP_SCR_EMPTY_THR); + goto finish_tx; + } + + /* Copy data to tx fifo */ + while (!(hci_h4p_inb(info, UART_OMAP_SSR) & UART_OMAP_SSR_TXFULL) && + (sent < skb->len)) { + hci_h4p_outb(info, UART_TX, skb->data[sent]); + sent++; + } + + info->hdev->stat.byte_tx += sent; + if (skb->len == sent) { + kfree_skb(skb); + } else { + skb_pull(skb, sent); + skb_queue_head(&info->txq, skb); + } + + hci_h4p_outb(info, UART_OMAP_SCR, hci_h4p_inb(info, UART_OMAP_SCR) & + ~UART_OMAP_SCR_EMPTY_THR); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + +finish_tx: + /* Flush posted write to avoid spurious interrupts */ + hci_h4p_inb(info, UART_OMAP_SCR); + +} + +static irqreturn_t hci_h4p_interrupt(int irq, void *data) +{ + struct hci_h4p_info *info = (struct hci_h4p_info *)data; + u8 iir, msr; + int ret; + + ret = IRQ_NONE; + + iir = hci_h4p_inb(info, UART_IIR); + if (iir & UART_IIR_NO_INT) + return IRQ_HANDLED; + + BT_DBG("In interrupt handler iir 0x%.2x", iir); + + iir &= UART_IIR_ID; + + if (iir == UART_IIR_MSI) { + msr = hci_h4p_inb(info, UART_MSR); + ret = IRQ_HANDLED; + } + if (iir == UART_IIR_RLSI) { + hci_h4p_inb(info, UART_RX); + hci_h4p_inb(info, UART_LSR); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_RDI) { + hci_h4p_rx_tasklet((unsigned long)data); + ret = IRQ_HANDLED; + } + + if (iir == UART_IIR_THRI) { + hci_h4p_tx_tasklet((unsigned long)data); + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t hci_h4p_wakeup_interrupt(int irq, void *dev_inst) +{ + struct hci_h4p_info *info = dev_inst; + int should_wakeup; + struct hci_dev *hdev; + + if (!info->hdev) + return IRQ_HANDLED; + + should_wakeup = gpio_get_value(info->host_wakeup_gpio); + hdev = info->hdev; + + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + if (should_wakeup == 1) + complete_all(&info->test_completion); + + return IRQ_HANDLED; + } + + BT_DBG("gpio interrupt %d", should_wakeup); + + /* Check if wee have missed some interrupts */ + if (info->rx_enabled == should_wakeup) + return IRQ_HANDLED; + + if (should_wakeup) + hci_h4p_enable_rx(info); + else + hci_h4p_disable_rx(info); + + return IRQ_HANDLED; +} + +static inline void hci_h4p_set_pm_limits(struct hci_h4p_info *info, bool set) +{ + struct hci_h4p_platform_data *bt_plat_data = info->dev->platform_data; + const char *sset = set ? "set" : "clear"; + + if (unlikely(!bt_plat_data || !bt_plat_data->set_pm_limits)) + return; + + if (set != !!test_bit(H4P_ACTIVE_MODE, &info->pm_flags)) { + bt_plat_data->set_pm_limits(info->dev, set); + if (set) + set_bit(H4P_ACTIVE_MODE, &info->pm_flags); + else + clear_bit(H4P_ACTIVE_MODE, &info->pm_flags); + BT_DBG("Change pm constraints to: %s", sset); + return; + } + + BT_DBG("pm constraints remains: %s", sset); +} + +static int hci_h4p_reset(struct hci_h4p_info *info) +{ + int err; + + err = hci_h4p_reset_uart(info); + if (err < 0) { + dev_err(info->dev, "Uart reset failed\n"); + return err; + } + hci_h4p_init_uart(info); + hci_h4p_set_rts(info, 0); + + gpio_set_value(info->reset_gpio, 0); + gpio_set_value(info->bt_wakeup_gpio, 1); + msleep(10); + + if (gpio_get_value(info->host_wakeup_gpio) == 1) { + dev_err(info->dev, "host_wakeup_gpio not low\n"); + return -EPROTO; + } + + init_completion(&info->test_completion); + gpio_set_value(info->reset_gpio, 1); + + if (!wait_for_completion_interruptible_timeout(&info->test_completion, + msecs_to_jiffies(100))) { + dev_err(info->dev, "wakeup test timed out\n"); + complete_all(&info->test_completion); + return -EPROTO; + } + + err = hci_h4p_wait_for_cts(info, 1, 100); + if (err < 0) { + dev_err(info->dev, "No cts from bt chip\n"); + return err; + } + + hci_h4p_set_rts(info, 1); + + return 0; +} + +/* hci callback functions */ +static int hci_h4p_hci_flush(struct hci_dev *hdev) +{ + struct hci_h4p_info *info = hci_get_drvdata(hdev); + skb_queue_purge(&info->txq); + + return 0; +} + +static int hci_h4p_bt_wakeup_test(struct hci_h4p_info *info) +{ + /* + * Test Sequence: + * Host de-asserts the BT_WAKE_UP line. + * Host polls the UART_CTS line, waiting for it to be de-asserted. + * Host asserts the BT_WAKE_UP line. + * Host polls the UART_CTS line, waiting for it to be asserted. + * Host de-asserts the BT_WAKE_UP line (allow the Bluetooth device to + * sleep). + * Host polls the UART_CTS line, waiting for it to be de-asserted. + */ + int err; + int ret = -ECOMM; + + if (!info) + return -EINVAL; + + /* Disable wakeup interrupts */ + disable_irq(gpio_to_irq(info->host_wakeup_gpio)); + + gpio_set_value(info->bt_wakeup_gpio, 0); + err = hci_h4p_wait_for_cts(info, 0, 100); + if (err) { + dev_warn(info->dev, "bt_wakeup_test: fail: " + "CTS low timed out: %d\n", err); + goto out; + } + + gpio_set_value(info->bt_wakeup_gpio, 1); + err = hci_h4p_wait_for_cts(info, 1, 100); + if (err) { + dev_warn(info->dev, "bt_wakeup_test: fail: " + "CTS high timed out: %d\n", err); + goto out; + } + + gpio_set_value(info->bt_wakeup_gpio, 0); + err = hci_h4p_wait_for_cts(info, 0, 100); + if (err) { + dev_warn(info->dev, "bt_wakeup_test: fail: " + "CTS re-low timed out: %d\n", err); + goto out; + } + + ret = 0; + +out: + + /* Re-enable wakeup interrupts */ + enable_irq(gpio_to_irq(info->host_wakeup_gpio)); + + return ret; +} + +static int hci_h4p_hci_open(struct hci_dev *hdev) +{ + struct hci_h4p_info *info; + int err, retries = 0; + struct sk_buff_head fw_queue; + unsigned long flags; + + info = hci_get_drvdata(hdev); + + if (test_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + /* TI1271 has HW bug and boot up might fail. Retry up to three times */ +again: + + info->rx_enabled = 1; + info->rx_state = WAIT_FOR_PKT_TYPE; + info->rx_count = 0; + info->garbage_bytes = 0; + info->rx_skb = NULL; + info->pm_enabled = 0; + init_completion(&info->fw_completion); + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + hci_h4p_set_clk(info, &info->rx_clocks_en, 1); + skb_queue_head_init(&fw_queue); + + err = hci_h4p_reset(info); + if (err < 0) + goto err_clean; + + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_CTS | UART_EFR_RTS); + info->autorts = 1; + + err = hci_h4p_send_negotiation(info); + + err = hci_h4p_read_fw(info, &fw_queue); + if (err < 0) { + dev_err(info->dev, "Cannot read firmware\n"); + goto err_clean; + } + + err = hci_h4p_send_fw(info, &fw_queue); + if (err < 0) { + dev_err(info->dev, "Sending firmware failed.\n"); + goto err_clean; + } + + info->pm_enabled = 1; + + err = hci_h4p_bt_wakeup_test(info); + if (err < 0) { + dev_err(info->dev, "BT wakeup test failed.\n"); + goto err_clean; + } + + spin_lock_irqsave(&info->lock, flags); + info->rx_enabled = gpio_get_value(info->host_wakeup_gpio); + hci_h4p_set_clk(info, &info->rx_clocks_en, info->rx_enabled); + spin_unlock_irqrestore(&info->lock, flags); + + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + + kfree_skb(info->alive_cmd_skb); + info->alive_cmd_skb = NULL; + set_bit(HCI_RUNNING, &hdev->flags); + + BT_DBG("hci up and running"); + return 0; + +err_clean: + hci_h4p_hci_flush(hdev); + hci_h4p_reset_uart(info); + del_timer_sync(&info->lazy_release); + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); + gpio_set_value(info->reset_gpio, 0); + gpio_set_value(info->bt_wakeup_gpio, 0); + skb_queue_purge(&fw_queue); + kfree_skb(info->alive_cmd_skb); + info->alive_cmd_skb = NULL; + kfree_skb(info->rx_skb); + info->rx_skb = NULL; + + if (retries++ < 3) { + dev_err(info->dev, "FW loading try %d fail. Retry.\n", retries); + goto again; + } + + return err; +} + +static int hci_h4p_hci_close(struct hci_dev *hdev) +{ + struct hci_h4p_info *info = hci_get_drvdata(hdev); + + if (!test_and_clear_bit(HCI_RUNNING, &hdev->flags)) + return 0; + + hci_h4p_hci_flush(hdev); + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + hci_h4p_set_clk(info, &info->rx_clocks_en, 1); + hci_h4p_reset_uart(info); + del_timer_sync(&info->lazy_release); + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + hci_h4p_set_clk(info, &info->rx_clocks_en, 0); + gpio_set_value(info->reset_gpio, 0); + gpio_set_value(info->bt_wakeup_gpio, 0); + kfree_skb(info->rx_skb); + + return 0; +} + +static int hci_h4p_hci_send_frame(struct hci_dev *hdev, struct sk_buff *skb) +{ + struct hci_h4p_info *info; + int err = 0; + + BT_DBG("dev %p, skb %p", hdev, skb); + + info = hci_get_drvdata(hdev); + + if (!test_bit(HCI_RUNNING, &hdev->flags)) { + dev_warn(info->dev, "Frame for non-running device\n"); + return -EIO; + } + + switch (bt_cb(skb)->pkt_type) { + case HCI_COMMAND_PKT: + hdev->stat.cmd_tx++; + break; + case HCI_ACLDATA_PKT: + hdev->stat.acl_tx++; + break; + case HCI_SCODATA_PKT: + hdev->stat.sco_tx++; + break; + } + + /* Push frame type to skb */ + *skb_push(skb, 1) = (bt_cb(skb)->pkt_type); + /* We should allways send word aligned data to h4+ devices */ + if (skb->len % 2) { + err = skb_pad(skb, 1); + if (!err) + *skb_put(skb, 1) = 0x00; + } + if (err) + return err; + + skb_queue_tail(&info->txq, skb); + hci_h4p_enable_tx(info); + + return 0; +} + +static ssize_t hci_h4p_store_bdaddr(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct hci_h4p_info *info = dev_get_drvdata(dev); + unsigned int bdaddr[6]; + int ret, i; + + ret = sscanf(buf, "%2x:%2x:%2x:%2x:%2x:%2x\n", + &bdaddr[0], &bdaddr[1], &bdaddr[2], + &bdaddr[3], &bdaddr[4], &bdaddr[5]); + + if (ret != 6) + return -EINVAL; + + for (i = 0; i < 6; i++) { + if (bdaddr[i] > 0xff) + return -EINVAL; + info->bd_addr[i] = bdaddr[i] & 0xff; + } + + return count; +} + +static ssize_t hci_h4p_show_bdaddr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hci_h4p_info *info = dev_get_drvdata(dev); + + return sprintf(buf, "%pMR\n", info->bd_addr); +} + +static DEVICE_ATTR(bdaddr, S_IRUGO | S_IWUSR, hci_h4p_show_bdaddr, + hci_h4p_store_bdaddr); + +static int hci_h4p_sysfs_create_files(struct device *dev) +{ + return device_create_file(dev, &dev_attr_bdaddr); +} + +static void hci_h4p_sysfs_remove_files(struct device *dev) +{ + device_remove_file(dev, &dev_attr_bdaddr); +} + +static int hci_h4p_register_hdev(struct hci_h4p_info *info) +{ + struct hci_dev *hdev; + + /* Initialize and register HCI device */ + + hdev = hci_alloc_dev(); + if (!hdev) { + dev_err(info->dev, "Can't allocate memory for device\n"); + return -ENOMEM; + } + info->hdev = hdev; + + hdev->bus = HCI_UART; + hci_set_drvdata(hdev, info); + + hdev->open = hci_h4p_hci_open; + hdev->close = hci_h4p_hci_close; + hdev->flush = hci_h4p_hci_flush; + hdev->send = hci_h4p_hci_send_frame; + set_bit(HCI_QUIRK_RESET_ON_CLOSE, &hdev->quirks); + + SET_HCIDEV_DEV(hdev, info->dev); + + if (hci_h4p_sysfs_create_files(info->dev) < 0) { + dev_err(info->dev, "failed to create sysfs files\n"); + goto free; + } + + if (hci_register_dev(hdev) >= 0) + return 0; + + dev_err(info->dev, "hci_register failed %s.\n", hdev->name); + hci_h4p_sysfs_remove_files(info->dev); +free: + hci_free_dev(info->hdev); + return -ENODEV; +} + +static int hci_h4p_probe(struct platform_device *pdev) +{ + struct hci_h4p_platform_data *bt_plat_data; + struct hci_h4p_info *info; + int err; + + dev_info(&pdev->dev, "Registering HCI H4P device\n"); + info = devm_kzalloc(&pdev->dev, sizeof(struct hci_h4p_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + info->tx_enabled = 1; + info->rx_enabled = 1; + spin_lock_init(&info->lock); + spin_lock_init(&info->clocks_lock); + skb_queue_head_init(&info->txq); + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "Could not get Bluetooth config data\n"); + return -ENODATA; + } + + bt_plat_data = pdev->dev.platform_data; + info->chip_type = bt_plat_data->chip_type; + info->bt_wakeup_gpio = bt_plat_data->bt_wakeup_gpio; + info->host_wakeup_gpio = bt_plat_data->host_wakeup_gpio; + info->reset_gpio = bt_plat_data->reset_gpio; + info->reset_gpio_shared = bt_plat_data->reset_gpio_shared; + info->bt_sysclk = bt_plat_data->bt_sysclk; + + BT_DBG("RESET gpio: %d", info->reset_gpio); + BT_DBG("BTWU gpio: %d", info->bt_wakeup_gpio); + BT_DBG("HOSTWU gpio: %d", info->host_wakeup_gpio); + BT_DBG("sysclk: %d", info->bt_sysclk); + + init_completion(&info->test_completion); + complete_all(&info->test_completion); + + if (!info->reset_gpio_shared) { + err = devm_gpio_request_one(&pdev->dev, info->reset_gpio, + GPIOF_OUT_INIT_LOW, "bt_reset"); + if (err < 0) { + dev_err(&pdev->dev, "Cannot get GPIO line %d\n", + info->reset_gpio); + return err; + } + } + + err = devm_gpio_request_one(&pdev->dev, info->bt_wakeup_gpio, + GPIOF_OUT_INIT_LOW, "bt_wakeup"); + + if (err < 0) { + dev_err(info->dev, "Cannot get GPIO line 0x%d", + info->bt_wakeup_gpio); + return err; + } + + err = devm_gpio_request_one(&pdev->dev, info->host_wakeup_gpio, + GPIOF_DIR_IN, "host_wakeup"); + if (err < 0) { + dev_err(info->dev, "Cannot get GPIO line %d", + info->host_wakeup_gpio); + return err; + } + + info->irq = bt_plat_data->uart_irq; + info->uart_base = devm_ioremap(&pdev->dev, bt_plat_data->uart_base, SZ_2K); + info->uart_iclk = devm_clk_get(&pdev->dev, bt_plat_data->uart_iclk); + info->uart_fclk = devm_clk_get(&pdev->dev, bt_plat_data->uart_fclk); + + err = devm_request_irq(&pdev->dev, info->irq, hci_h4p_interrupt, IRQF_DISABLED, + "hci_h4p", info); + if (err < 0) { + dev_err(info->dev, "hci_h4p: unable to get IRQ %d\n", info->irq); + return err; + } + + err = devm_request_irq(&pdev->dev, gpio_to_irq(info->host_wakeup_gpio), + hci_h4p_wakeup_interrupt, IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_DISABLED, + "hci_h4p_wkup", info); + if (err < 0) { + dev_err(info->dev, "hci_h4p: unable to get wakeup IRQ %d\n", + gpio_to_irq(info->host_wakeup_gpio)); + return err; + } + + err = irq_set_irq_wake(gpio_to_irq(info->host_wakeup_gpio), 1); + if (err < 0) { + dev_err(info->dev, "hci_h4p: unable to set wakeup for IRQ %d\n", + gpio_to_irq(info->host_wakeup_gpio)); + return err; + } + + init_timer_deferrable(&info->lazy_release); + info->lazy_release.function = hci_h4p_lazy_clock_release; + info->lazy_release.data = (unsigned long)info; + hci_h4p_set_clk(info, &info->tx_clocks_en, 1); + err = hci_h4p_reset_uart(info); + if (err < 0) + return err; + gpio_set_value(info->reset_gpio, 0); + hci_h4p_set_clk(info, &info->tx_clocks_en, 0); + + platform_set_drvdata(pdev, info); + + if (hci_h4p_register_hdev(info) < 0) { + dev_err(info->dev, "failed to register hci_h4p hci device\n"); + return -EINVAL; + } + + return 0; +} + +static int hci_h4p_remove(struct platform_device *pdev) +{ + struct hci_h4p_info *info; + + info = platform_get_drvdata(pdev); + + hci_h4p_sysfs_remove_files(info->dev); + hci_h4p_hci_close(info->hdev); + hci_unregister_dev(info->hdev); + hci_free_dev(info->hdev); + + return 0; +} + +static struct platform_driver hci_h4p_driver = { + .probe = hci_h4p_probe, + .remove = hci_h4p_remove, + .driver = { + .name = "hci_h4p", + }, +}; + +module_platform_driver(hci_h4p_driver); + +MODULE_ALIAS("platform:hci_h4p"); +MODULE_DESCRIPTION("Bluetooth h4 driver with nokia extensions"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ville Tervo"); +MODULE_FIRMWARE(FW_NAME_TI1271_PRELE); +MODULE_FIRMWARE(FW_NAME_TI1271_LE); +MODULE_FIRMWARE(FW_NAME_TI1271); +MODULE_FIRMWARE(FW_NAME_BCM2048); +MODULE_FIRMWARE(FW_NAME_CSR); diff --git a/drivers/staging/nokia_h4p/nokia_fw-bcm.c b/drivers/staging/nokia_h4p/nokia_fw-bcm.c new file mode 100644 index 000000000000..e8912bfc0a91 --- /dev/null +++ b/drivers/staging/nokia_h4p/nokia_fw-bcm.c @@ -0,0 +1,147 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2005-2008 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "hci_h4p.h" + +static int hci_h4p_bcm_set_bdaddr(struct hci_h4p_info *info, struct sk_buff *skb) +{ + int i; + static const u8 nokia_oui[3] = {0x00, 0x1f, 0xdf}; + int not_valid; + + not_valid = 1; + for (i = 0; i < 6; i++) { + if (info->bd_addr[i] != 0x00) { + not_valid = 0; + break; + } + } + + if (not_valid) { + dev_info(info->dev, "Valid bluetooth address not found, setting some random\n"); + /* When address is not valid, use some random but Nokia MAC */ + memcpy(info->bd_addr, nokia_oui, 3); + get_random_bytes(info->bd_addr + 3, 3); + } + + for (i = 0; i < 6; i++) + skb->data[9 - i] = info->bd_addr[i]; + + return 0; +} + +void hci_h4p_bcm_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb) +{ + struct sk_buff *fw_skb; + int err; + unsigned long flags; + + if (skb->data[5] != 0x00) { + dev_err(info->dev, "Firmware sending command failed 0x%.2x\n", + skb->data[5]); + info->fw_error = -EPROTO; + } + + kfree_skb(skb); + + fw_skb = skb_dequeue(info->fw_q); + if (fw_skb == NULL || info->fw_error) { + complete(&info->fw_completion); + return; + } + + if (fw_skb->data[1] == 0x01 && fw_skb->data[2] == 0xfc && fw_skb->len >= 10) { + BT_DBG("Setting bluetooth address"); + err = hci_h4p_bcm_set_bdaddr(info, fw_skb); + if (err < 0) { + kfree_skb(fw_skb); + info->fw_error = err; + complete(&info->fw_completion); + return; + } + } + + skb_queue_tail(&info->txq, fw_skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); +} + + +int hci_h4p_bcm_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue) +{ + struct sk_buff *skb; + unsigned long flags, time; + + info->fw_error = 0; + + BT_DBG("Sending firmware"); + + time = jiffies; + + info->fw_q = fw_queue; + skb = skb_dequeue(fw_queue); + if (!skb) + return -ENODATA; + + BT_DBG("Sending commands"); + + /* + * Disable smart-idle as UART TX interrupts + * are not wake-up capable + */ + hci_h4p_smart_idle(info, 0); + + /* Check if this is bd_address packet */ + init_completion(&info->fw_completion); + skb_queue_tail(&info->txq, skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + + if (!wait_for_completion_timeout(&info->fw_completion, + msecs_to_jiffies(2000))) { + dev_err(info->dev, "No reply to fw command\n"); + return -ETIMEDOUT; + } + + if (info->fw_error) { + dev_err(info->dev, "FW error\n"); + return -EPROTO; + } + + BT_DBG("Firmware sent in %d msecs", + jiffies_to_msecs(jiffies-time)); + + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS); + hci_h4p_set_rts(info, 0); + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE); + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS); + + return 0; +} diff --git a/drivers/staging/nokia_h4p/nokia_fw-csr.c b/drivers/staging/nokia_h4p/nokia_fw-csr.c new file mode 100644 index 000000000000..e39c4a31a879 --- /dev/null +++ b/drivers/staging/nokia_h4p/nokia_fw-csr.c @@ -0,0 +1,150 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2005-2008 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "hci_h4p.h" + +void hci_h4p_bc4_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb) +{ + /* Check if this is fw packet */ + if (skb->data[0] != 0xff) { + hci_recv_frame(info->hdev, skb); + return; + } + + if (skb->data[11] || skb->data[12]) { + dev_err(info->dev, "Firmware sending command failed\n"); + info->fw_error = -EPROTO; + } + + kfree_skb(skb); + complete(&info->fw_completion); +} + +int hci_h4p_bc4_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue) +{ + static const u8 nokia_oui[3] = {0x00, 0x19, 0x4F}; + struct sk_buff *skb; + unsigned int offset; + int retries, count, i, not_valid; + unsigned long flags; + + info->fw_error = 0; + + BT_DBG("Sending firmware"); + skb = skb_dequeue(fw_queue); + + if (!skb) + return -ENOMSG; + + /* Check if this is bd_address packet */ + if (skb->data[15] == 0x01 && skb->data[16] == 0x00) { + offset = 21; + skb->data[offset + 1] = 0x00; + skb->data[offset + 5] = 0x00; + + not_valid = 1; + for (i = 0; i < 6; i++) { + if (info->bd_addr[i] != 0x00) { + not_valid = 0; + break; + } + } + + if (not_valid) { + dev_info(info->dev, "Valid bluetooth address not found," + " setting some random\n"); + /* When address is not valid, use some random */ + memcpy(info->bd_addr, nokia_oui, 3); + get_random_bytes(info->bd_addr + 3, 3); + } + + skb->data[offset + 7] = info->bd_addr[0]; + skb->data[offset + 6] = info->bd_addr[1]; + skb->data[offset + 4] = info->bd_addr[2]; + skb->data[offset + 0] = info->bd_addr[3]; + skb->data[offset + 3] = info->bd_addr[4]; + skb->data[offset + 2] = info->bd_addr[5]; + } + + for (count = 1; ; count++) { + BT_DBG("Sending firmware command %d", count); + init_completion(&info->fw_completion); + skb_queue_tail(&info->txq, skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + + skb = skb_dequeue(fw_queue); + if (!skb) + break; + + if (!wait_for_completion_timeout(&info->fw_completion, + msecs_to_jiffies(1000))) { + dev_err(info->dev, "No reply to fw command\n"); + return -ETIMEDOUT; + } + + if (info->fw_error) { + dev_err(info->dev, "FW error\n"); + return -EPROTO; + } + }; + + /* Wait for chip warm reset */ + retries = 100; + while ((!skb_queue_empty(&info->txq) || + !(hci_h4p_inb(info, UART_LSR) & UART_LSR_TEMT)) && + retries--) { + msleep(10); + } + if (!retries) { + dev_err(info->dev, "Transmitter not empty\n"); + return -ETIMEDOUT; + } + + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE); + + if (hci_h4p_wait_for_cts(info, 1, 100)) { + dev_err(info->dev, "cts didn't deassert after final speed\n"); + return -ETIMEDOUT; + } + + retries = 100; + do { + init_completion(&info->init_completion); + hci_h4p_send_alive_packet(info); + retries--; + } while (!wait_for_completion_timeout(&info->init_completion, 100) && + retries > 0); + + if (!retries) { + dev_err(info->dev, "No alive reply after speed change\n"); + return -ETIMEDOUT; + } + + return 0; +} diff --git a/drivers/staging/nokia_h4p/nokia_fw-ti1273.c b/drivers/staging/nokia_h4p/nokia_fw-ti1273.c new file mode 100644 index 000000000000..f5500f71c839 --- /dev/null +++ b/drivers/staging/nokia_h4p/nokia_fw-ti1273.c @@ -0,0 +1,110 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2009 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include "hci_h4p.h" + +static struct sk_buff_head *fw_q; + +void hci_h4p_ti1273_parse_fw_event(struct hci_h4p_info *info, + struct sk_buff *skb) +{ + struct sk_buff *fw_skb; + unsigned long flags; + + if (skb->data[5] != 0x00) { + dev_err(info->dev, "Firmware sending command failed 0x%.2x\n", + skb->data[5]); + info->fw_error = -EPROTO; + } + + kfree_skb(skb); + + fw_skb = skb_dequeue(fw_q); + if (fw_skb == NULL || info->fw_error) { + complete(&info->fw_completion); + return; + } + + skb_queue_tail(&info->txq, fw_skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); +} + + +int hci_h4p_ti1273_send_fw(struct hci_h4p_info *info, + struct sk_buff_head *fw_queue) +{ + struct sk_buff *skb; + unsigned long flags, time; + + info->fw_error = 0; + + BT_DBG("Sending firmware"); + + time = jiffies; + + fw_q = fw_queue; + skb = skb_dequeue(fw_queue); + if (!skb) + return -ENODATA; + + BT_DBG("Sending commands"); + /* Check if this is bd_address packet */ + init_completion(&info->fw_completion); + hci_h4p_smart_idle(info, 0); + skb_queue_tail(&info->txq, skb); + spin_lock_irqsave(&info->lock, flags); + hci_h4p_outb(info, UART_IER, hci_h4p_inb(info, UART_IER) | + UART_IER_THRI); + spin_unlock_irqrestore(&info->lock, flags); + + if (!wait_for_completion_timeout(&info->fw_completion, + msecs_to_jiffies(2000))) { + dev_err(info->dev, "No reply to fw command\n"); + return -ETIMEDOUT; + } + + if (info->fw_error) { + dev_err(info->dev, "FW error\n"); + return -EPROTO; + } + + BT_DBG("Firmware sent in %d msecs", + jiffies_to_msecs(jiffies-time)); + + hci_h4p_set_auto_ctsrts(info, 0, UART_EFR_RTS); + hci_h4p_set_rts(info, 0); + hci_h4p_change_speed(info, BC4_MAX_BAUD_RATE); + if (hci_h4p_wait_for_cts(info, 1, 100)) { + dev_err(info->dev, + "cts didn't go down after final speed change\n"); + return -ETIMEDOUT; + } + hci_h4p_set_auto_ctsrts(info, 1, UART_EFR_RTS); + + return 0; +} diff --git a/drivers/staging/nokia_h4p/nokia_fw.c b/drivers/staging/nokia_h4p/nokia_fw.c new file mode 100644 index 000000000000..cfea61cd59e9 --- /dev/null +++ b/drivers/staging/nokia_h4p/nokia_fw.c @@ -0,0 +1,195 @@ +/* + * This file is part of hci_h4p bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * Contact: Ville Tervo + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include + +#include "hci_h4p.h" + +static int fw_pos; + +/* Firmware handling */ +static int hci_h4p_open_firmware(struct hci_h4p_info *info, + const struct firmware **fw_entry) +{ + int err; + + fw_pos = 0; + BT_DBG("Opening firmware man_id 0x%.2x ver_id 0x%.2x", + info->man_id, info->ver_id); + switch (info->man_id) { + case H4P_ID_TI1271: + switch (info->ver_id) { + case 0xe1: + err = request_firmware(fw_entry, FW_NAME_TI1271_PRELE, + info->dev); + break; + case 0xd1: + case 0xf1: + err = request_firmware(fw_entry, FW_NAME_TI1271_LE, + info->dev); + break; + default: + err = request_firmware(fw_entry, FW_NAME_TI1271, + info->dev); + } + break; + case H4P_ID_CSR: + err = request_firmware(fw_entry, FW_NAME_CSR, info->dev); + break; + case H4P_ID_BCM2048: + err = request_firmware(fw_entry, FW_NAME_BCM2048, info->dev); + break; + default: + dev_err(info->dev, "Invalid chip type\n"); + *fw_entry = NULL; + err = -EINVAL; + } + + return err; +} + +static void hci_h4p_close_firmware(const struct firmware *fw_entry) +{ + release_firmware(fw_entry); +} + +/* Read fw. Return length of the command. If no more commands in + * fw 0 is returned. In error case return value is negative. + */ +static int hci_h4p_read_fw_cmd(struct hci_h4p_info *info, struct sk_buff **skb, + const struct firmware *fw_entry, gfp_t how) +{ + unsigned int cmd_len; + + if (fw_pos >= fw_entry->size) + return 0; + + if (fw_pos + 2 > fw_entry->size) { + dev_err(info->dev, "Corrupted firmware image 1\n"); + return -EMSGSIZE; + } + + cmd_len = fw_entry->data[fw_pos++]; + cmd_len += fw_entry->data[fw_pos++] << 8; + if (cmd_len == 0) + return 0; + + if (fw_pos + cmd_len > fw_entry->size) { + dev_err(info->dev, "Corrupted firmware image 2\n"); + return -EMSGSIZE; + } + + *skb = bt_skb_alloc(cmd_len, how); + if (!*skb) { + dev_err(info->dev, "Cannot reserve memory for buffer\n"); + return -ENOMEM; + } + memcpy(skb_put(*skb, cmd_len), &fw_entry->data[fw_pos], cmd_len); + + fw_pos += cmd_len; + + return (*skb)->len; +} + +int hci_h4p_read_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue) +{ + const struct firmware *fw_entry = NULL; + struct sk_buff *skb = NULL; + int err; + + err = hci_h4p_open_firmware(info, &fw_entry); + if (err < 0 || !fw_entry) + goto err_clean; + + while ((err = hci_h4p_read_fw_cmd(info, &skb, fw_entry, GFP_KERNEL))) { + if (err < 0 || !skb) + goto err_clean; + + skb_queue_tail(fw_queue, skb); + } + + /* Chip detection code does neg and alive stuff + * discard two first skbs */ + skb = skb_dequeue(fw_queue); + if (!skb) { + err = -EMSGSIZE; + goto err_clean; + } + kfree_skb(skb); + skb = skb_dequeue(fw_queue); + if (!skb) { + err = -EMSGSIZE; + goto err_clean; + } + kfree_skb(skb); + +err_clean: + hci_h4p_close_firmware(fw_entry); + return err; +} + +int hci_h4p_send_fw(struct hci_h4p_info *info, struct sk_buff_head *fw_queue) +{ + int err; + + switch (info->man_id) { + case H4P_ID_CSR: + err = hci_h4p_bc4_send_fw(info, fw_queue); + break; + case H4P_ID_TI1271: + err = hci_h4p_ti1273_send_fw(info, fw_queue); + break; + case H4P_ID_BCM2048: + err = hci_h4p_bcm_send_fw(info, fw_queue); + break; + default: + dev_err(info->dev, "Don't know how to send firmware\n"); + err = -EINVAL; + } + + return err; +} + +void hci_h4p_parse_fw_event(struct hci_h4p_info *info, struct sk_buff *skb) +{ + switch (info->man_id) { + case H4P_ID_CSR: + hci_h4p_bc4_parse_fw_event(info, skb); + break; + case H4P_ID_TI1271: + hci_h4p_ti1273_parse_fw_event(info, skb); + break; + case H4P_ID_BCM2048: + hci_h4p_bcm_parse_fw_event(info, skb); + break; + default: + dev_err(info->dev, "Don't know how to parse fw event\n"); + info->fw_error = -EINVAL; + } + + return; +} diff --git a/drivers/staging/nokia_h4p/nokia_uart.c b/drivers/staging/nokia_h4p/nokia_uart.c new file mode 100644 index 000000000000..0fb57de4b750 --- /dev/null +++ b/drivers/staging/nokia_h4p/nokia_uart.c @@ -0,0 +1,199 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2005, 2006 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include + +#include "hci_h4p.h" + +inline void hci_h4p_outb(struct hci_h4p_info *info, unsigned int offset, u8 val) +{ + __raw_writeb(val, info->uart_base + (offset << 2)); +} + +inline u8 hci_h4p_inb(struct hci_h4p_info *info, unsigned int offset) +{ + return __raw_readb(info->uart_base + (offset << 2)); +} + +void hci_h4p_set_rts(struct hci_h4p_info *info, int active) +{ + u8 b; + + b = hci_h4p_inb(info, UART_MCR); + if (active) + b |= UART_MCR_RTS; + else + b &= ~UART_MCR_RTS; + hci_h4p_outb(info, UART_MCR, b); +} + +int hci_h4p_wait_for_cts(struct hci_h4p_info *info, int active, + int timeout_ms) +{ + unsigned long timeout; + int state; + + timeout = jiffies + msecs_to_jiffies(timeout_ms); + for (;;) { + state = hci_h4p_inb(info, UART_MSR) & UART_MSR_CTS; + if (active) { + if (state) + return 0; + } else { + if (!state) + return 0; + } + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + msleep(1); + } +} + +void __hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which) +{ + u8 lcr, b; + + lcr = hci_h4p_inb(info, UART_LCR); + hci_h4p_outb(info, UART_LCR, 0xbf); + b = hci_h4p_inb(info, UART_EFR); + if (on) + b |= which; + else + b &= ~which; + hci_h4p_outb(info, UART_EFR, b); + hci_h4p_outb(info, UART_LCR, lcr); +} + +void hci_h4p_set_auto_ctsrts(struct hci_h4p_info *info, int on, u8 which) +{ + unsigned long flags; + + spin_lock_irqsave(&info->lock, flags); + __hci_h4p_set_auto_ctsrts(info, on, which); + spin_unlock_irqrestore(&info->lock, flags); +} + +void hci_h4p_change_speed(struct hci_h4p_info *info, unsigned long speed) +{ + unsigned int divisor; + u8 lcr, mdr1; + + BT_DBG("Setting speed %lu", speed); + + if (speed >= 460800) { + divisor = UART_CLOCK / 13 / speed; + mdr1 = 3; + } else { + divisor = UART_CLOCK / 16 / speed; + mdr1 = 0; + } + + /* Make sure UART mode is disabled */ + hci_h4p_outb(info, UART_OMAP_MDR1, 7); + + lcr = hci_h4p_inb(info, UART_LCR); + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB); /* Set DLAB */ + hci_h4p_outb(info, UART_DLL, divisor & 0xff); /* Set speed */ + hci_h4p_outb(info, UART_DLM, divisor >> 8); + hci_h4p_outb(info, UART_LCR, lcr); + + /* Make sure UART mode is enabled */ + hci_h4p_outb(info, UART_OMAP_MDR1, mdr1); +} + +int hci_h4p_reset_uart(struct hci_h4p_info *info) +{ + int count = 0; + + /* Reset the UART */ + hci_h4p_outb(info, UART_OMAP_SYSC, UART_SYSC_OMAP_RESET); + while (!(hci_h4p_inb(info, UART_OMAP_SYSS) & UART_SYSS_RESETDONE)) { + if (count++ > 100) { + dev_err(info->dev, "hci_h4p: UART reset timeout\n"); + return -ENODEV; + } + udelay(1); + } + + return 0; +} + +void hci_h4p_store_regs(struct hci_h4p_info *info) +{ + u16 lcr = 0; + + lcr = hci_h4p_inb(info, UART_LCR); + hci_h4p_outb(info, UART_LCR, 0xBF); + info->dll = hci_h4p_inb(info, UART_DLL); + info->dlh = hci_h4p_inb(info, UART_DLM); + info->efr = hci_h4p_inb(info, UART_EFR); + hci_h4p_outb(info, UART_LCR, lcr); + info->mdr1 = hci_h4p_inb(info, UART_OMAP_MDR1); + info->ier = hci_h4p_inb(info, UART_IER); +} + +void hci_h4p_restore_regs(struct hci_h4p_info *info) +{ + u16 lcr = 0; + + hci_h4p_init_uart(info); + + hci_h4p_outb(info, UART_OMAP_MDR1, 7); + lcr = hci_h4p_inb(info, UART_LCR); + hci_h4p_outb(info, UART_LCR, 0xBF); + hci_h4p_outb(info, UART_DLL, info->dll); /* Set speed */ + hci_h4p_outb(info, UART_DLM, info->dlh); + hci_h4p_outb(info, UART_EFR, info->efr); + hci_h4p_outb(info, UART_LCR, lcr); + hci_h4p_outb(info, UART_OMAP_MDR1, info->mdr1); + hci_h4p_outb(info, UART_IER, info->ier); +} + +void hci_h4p_init_uart(struct hci_h4p_info *info) +{ + u8 mcr, efr; + + /* Enable and setup FIFO */ + hci_h4p_outb(info, UART_OMAP_MDR1, 0x00); + + hci_h4p_outb(info, UART_LCR, 0xbf); + efr = hci_h4p_inb(info, UART_EFR); + hci_h4p_outb(info, UART_EFR, UART_EFR_ECB); + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB); + mcr = hci_h4p_inb(info, UART_MCR); + hci_h4p_outb(info, UART_MCR, UART_MCR_TCRTLR); + hci_h4p_outb(info, UART_FCR, UART_FCR_ENABLE_FIFO | + UART_FCR_CLEAR_RCVR | UART_FCR_CLEAR_XMIT | + (3 << 6) | (0 << 4)); + hci_h4p_outb(info, UART_LCR, 0xbf); + hci_h4p_outb(info, UART_TI752_TLR, 0xed); + hci_h4p_outb(info, UART_TI752_TCR, 0xef); + hci_h4p_outb(info, UART_EFR, efr); + hci_h4p_outb(info, UART_LCR, UART_LCR_DLAB); + hci_h4p_outb(info, UART_MCR, 0x00); + hci_h4p_outb(info, UART_LCR, UART_LCR_WLEN8); + hci_h4p_outb(info, UART_IER, UART_IER_RDI); + hci_h4p_outb(info, UART_OMAP_SYSC, (1 << 0) | (1 << 2) | (2 << 3)); +} diff --git a/include/linux/platform_data/bt-nokia-h4p.h b/include/linux/platform_data/bt-nokia-h4p.h new file mode 100644 index 000000000000..30d169dfadf3 --- /dev/null +++ b/include/linux/platform_data/bt-nokia-h4p.h @@ -0,0 +1,38 @@ +/* + * This file is part of Nokia H4P bluetooth driver + * + * Copyright (C) 2010 Nokia Corporation. + * + * 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, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + + +/** + * struct hci_h4p_platform data - hci_h4p Platform data structure + */ +struct hci_h4p_platform_data { + int chip_type; + int bt_sysclk; + unsigned int bt_wakeup_gpio; + unsigned int host_wakeup_gpio; + unsigned int reset_gpio; + int reset_gpio_shared; + unsigned int uart_irq; + phys_addr_t uart_base; + const char *uart_iclk; + const char *uart_fclk; + void (*set_pm_limits)(struct device *dev, bool set); +}; -- cgit v1.2.3 From 9fe21fdc5f3d3aa7d6e78ad25668e234330b6974 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 21 Dec 2013 15:08:00 +0400 Subject: video: imxfb: Use regulator API with LCD class for powering This patch replaces custom lcd_power() callback with regulator API over LCD class. Signed-off-by: Alexander Shiyan Acked-by: Shawn Guo Signed-off-by: Tomi Valkeinen --- .../devicetree/bindings/video/fsl,imx-fb.txt | 1 + arch/arm/mach-imx/mach-mx27ads.c | 55 +++++++++++++++-- drivers/video/imxfb.c | 71 +++++++++++++++++++--- include/linux/platform_data/video-imxfb.h | 1 - 4 files changed, 114 insertions(+), 14 deletions(-) (limited to 'include/linux/platform_data') diff --git a/Documentation/devicetree/bindings/video/fsl,imx-fb.txt b/Documentation/devicetree/bindings/video/fsl,imx-fb.txt index 46da08db186a..e6b1ee9b8e2e 100644 --- a/Documentation/devicetree/bindings/video/fsl,imx-fb.txt +++ b/Documentation/devicetree/bindings/video/fsl,imx-fb.txt @@ -15,6 +15,7 @@ Required nodes: - fsl,pcr: LCDC PCR value Optional properties: +- lcd-supply: Regulator for LCD supply voltage. - fsl,dmacr: DMA Control Register value. This is optional. By default, the register is not modified as recommended by the datasheet. - fsl,lscr1: LCDC Sharp Configuration Register value. diff --git a/arch/arm/mach-imx/mach-mx27ads.c b/arch/arm/mach-imx/mach-mx27ads.c index 9821b824dcaf..a7a4a9c67615 100644 --- a/arch/arm/mach-imx/mach-mx27ads.c +++ b/arch/arm/mach-imx/mach-mx27ads.c @@ -21,6 +21,10 @@ #include #include #include + +#include +#include + #include #include #include @@ -195,14 +199,58 @@ static const struct imxi2c_platform_data mx27ads_i2c1_data __initconst = { static struct i2c_board_info mx27ads_i2c_devices[] = { }; -void lcd_power(int on) +static void vgpio_set(struct gpio_chip *chip, unsigned offset, int value) { - if (on) + if (value) __raw_writew(PBC_BCTRL1_LCDON, PBC_BCTRL1_SET_REG); else __raw_writew(PBC_BCTRL1_LCDON, PBC_BCTRL1_CLEAR_REG); } +static int vgpio_dir_out(struct gpio_chip *chip, unsigned offset, int value) +{ + return 0; +} + +#define MX27ADS_LCD_GPIO (6 * 32) + +static struct regulator_consumer_supply mx27ads_lcd_regulator_consumer = + REGULATOR_SUPPLY("lcd", "imx-fb.0"); + +static struct regulator_init_data mx27ads_lcd_regulator_init_data = { + .constraints = { + .valid_ops_mask = REGULATOR_CHANGE_STATUS, +}, + .consumer_supplies = &mx27ads_lcd_regulator_consumer, + .num_consumer_supplies = 1, +}; + +static struct fixed_voltage_config mx27ads_lcd_regulator_pdata = { + .supply_name = "LCD", + .microvolts = 3300000, + .gpio = MX27ADS_LCD_GPIO, + .init_data = &mx27ads_lcd_regulator_init_data, +}; + +static void __init mx27ads_regulator_init(void) +{ + struct gpio_chip *vchip; + + vchip = kzalloc(sizeof(*vchip), GFP_KERNEL); + vchip->owner = THIS_MODULE; + vchip->label = "LCD"; + vchip->base = MX27ADS_LCD_GPIO; + vchip->ngpio = 1; + vchip->direction_output = vgpio_dir_out; + vchip->set = vgpio_set; + gpiochip_add(vchip); + + platform_device_register_data(&platform_bus, "reg-fixed-voltage", + PLATFORM_DEVID_AUTO, + &mx27ads_lcd_regulator_pdata, + sizeof(mx27ads_lcd_regulator_pdata)); +} + static struct imx_fb_videomode mx27ads_modes[] = { { .mode = { @@ -239,8 +287,6 @@ static const struct imx_fb_platform_data mx27ads_fb_data __initconst = { .pwmr = 0x00A903FF, .lscr1 = 0x00120300, .dmacr = 0x00020010, - - .lcd_power = lcd_power, }; static int mx27ads_sdhc1_init(struct device *dev, irq_handler_t detect_irq, @@ -304,6 +350,7 @@ static void __init mx27ads_board_init(void) i2c_register_board_info(1, mx27ads_i2c_devices, ARRAY_SIZE(mx27ads_i2c_devices)); imx27_add_imx_i2c(1, &mx27ads_i2c1_data); + mx27ads_regulator_init(); imx27_add_imx_fb(&mx27ads_fb_data); imx27_add_mxc_mmc(0, &sdhc1_pdata); imx27_add_mxc_mmc(1, &sdhc2_pdata); diff --git a/drivers/video/imxfb.c b/drivers/video/imxfb.c index 44ee678481d5..e50b67fada51 100644 --- a/drivers/video/imxfb.c +++ b/drivers/video/imxfb.c @@ -30,10 +30,13 @@ #include #include #include +#include #include #include #include +#include + #include